162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Generic driver for NXP NCI NFC chips 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2014 NXP Semiconductors All rights reserved. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Clément Perrochaud <clement.perrochaud@nxp.com> 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Derived from PN544 device driver: 1062306a36Sopenharmony_ci * Copyright (C) 2012 Intel Corporation. All rights reserved. 1162306a36Sopenharmony_ci */ 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/completion.h> 1462306a36Sopenharmony_ci#include <linux/firmware.h> 1562306a36Sopenharmony_ci#include <linux/nfc.h> 1662306a36Sopenharmony_ci#include <asm/unaligned.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include "nxp-nci.h" 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci/* Crypto operations can take up to 30 seconds */ 2162306a36Sopenharmony_ci#define NXP_NCI_FW_ANSWER_TIMEOUT msecs_to_jiffies(30000) 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define NXP_NCI_FW_CMD_RESET 0xF0 2462306a36Sopenharmony_ci#define NXP_NCI_FW_CMD_GETVERSION 0xF1 2562306a36Sopenharmony_ci#define NXP_NCI_FW_CMD_CHECKINTEGRITY 0xE0 2662306a36Sopenharmony_ci#define NXP_NCI_FW_CMD_WRITE 0xC0 2762306a36Sopenharmony_ci#define NXP_NCI_FW_CMD_READ 0xA2 2862306a36Sopenharmony_ci#define NXP_NCI_FW_CMD_GETSESSIONSTATE 0xF2 2962306a36Sopenharmony_ci#define NXP_NCI_FW_CMD_LOG 0xA7 3062306a36Sopenharmony_ci#define NXP_NCI_FW_CMD_FORCE 0xD0 3162306a36Sopenharmony_ci#define NXP_NCI_FW_CMD_GET_DIE_ID 0xF4 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#define NXP_NCI_FW_CHUNK_FLAG 0x0400 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_OK 0x00 3662306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_INVALID_ADDR 0x01 3762306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_GENERIC_ERROR 0x02 3862306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_UNKNOWN_CMD 0x0B 3962306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_ABORTED_CMD 0x0C 4062306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_PLL_ERROR 0x0D 4162306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_ADDR_RANGE_OFL_ERROR 0x1E 4262306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_BUFFER_OFL_ERROR 0x1F 4362306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_MEM_BSY 0x20 4462306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_SIGNATURE_ERROR 0x21 4562306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_FIRMWARE_VERSION_ERROR 0x24 4662306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_PROTOCOL_ERROR 0x28 4762306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_SFWU_DEGRADED 0x2A 4862306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_PH_STATUS_FIRST_CHUNK 0x2D 4962306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_PH_STATUS_NEXT_CHUNK 0x2E 5062306a36Sopenharmony_ci#define NXP_NCI_FW_RESULT_PH_STATUS_INTERNAL_ERROR_5 0xC5 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_civoid nxp_nci_fw_work_complete(struct nxp_nci_info *info, int result) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci struct nxp_nci_fw_info *fw_info = &info->fw_info; 5562306a36Sopenharmony_ci int r; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci if (info->phy_ops->set_mode) { 5862306a36Sopenharmony_ci r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD); 5962306a36Sopenharmony_ci if (r < 0 && result == 0) 6062306a36Sopenharmony_ci result = -r; 6162306a36Sopenharmony_ci } 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci info->mode = NXP_NCI_MODE_COLD; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci if (fw_info->fw) { 6662306a36Sopenharmony_ci release_firmware(fw_info->fw); 6762306a36Sopenharmony_ci fw_info->fw = NULL; 6862306a36Sopenharmony_ci } 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci nfc_fw_download_done(info->ndev->nfc_dev, fw_info->name, (u32) -result); 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci/* crc_ccitt cannot be used since it is computed MSB first and not LSB first */ 7462306a36Sopenharmony_cistatic u16 nxp_nci_fw_crc(u8 const *buffer, size_t len) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci u16 crc = 0xffff; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci while (len--) { 7962306a36Sopenharmony_ci crc = ((crc >> 8) | (crc << 8)) ^ *buffer++; 8062306a36Sopenharmony_ci crc ^= (crc & 0xff) >> 4; 8162306a36Sopenharmony_ci crc ^= (crc & 0xff) << 12; 8262306a36Sopenharmony_ci crc ^= (crc & 0xff) << 5; 8362306a36Sopenharmony_ci } 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci return crc; 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic int nxp_nci_fw_send_chunk(struct nxp_nci_info *info) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci struct nxp_nci_fw_info *fw_info = &info->fw_info; 9162306a36Sopenharmony_ci u16 header, crc; 9262306a36Sopenharmony_ci struct sk_buff *skb; 9362306a36Sopenharmony_ci size_t chunk_len; 9462306a36Sopenharmony_ci size_t remaining_len; 9562306a36Sopenharmony_ci int r; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci skb = nci_skb_alloc(info->ndev, info->max_payload, GFP_KERNEL); 9862306a36Sopenharmony_ci if (!skb) 9962306a36Sopenharmony_ci return -ENOMEM; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci chunk_len = info->max_payload - NXP_NCI_FW_HDR_LEN - NXP_NCI_FW_CRC_LEN; 10262306a36Sopenharmony_ci remaining_len = fw_info->frame_size - fw_info->written; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci if (remaining_len > chunk_len) { 10562306a36Sopenharmony_ci header = NXP_NCI_FW_CHUNK_FLAG; 10662306a36Sopenharmony_ci } else { 10762306a36Sopenharmony_ci chunk_len = remaining_len; 10862306a36Sopenharmony_ci header = 0x0000; 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci header |= chunk_len & NXP_NCI_FW_FRAME_LEN_MASK; 11262306a36Sopenharmony_ci put_unaligned_be16(header, skb_put(skb, NXP_NCI_FW_HDR_LEN)); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci skb_put_data(skb, fw_info->data + fw_info->written, chunk_len); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci crc = nxp_nci_fw_crc(skb->data, chunk_len + NXP_NCI_FW_HDR_LEN); 11762306a36Sopenharmony_ci put_unaligned_be16(crc, skb_put(skb, NXP_NCI_FW_CRC_LEN)); 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci r = info->phy_ops->write(info->phy_id, skb); 12062306a36Sopenharmony_ci if (r >= 0) 12162306a36Sopenharmony_ci r = chunk_len; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci kfree_skb(skb); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci return r; 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic int nxp_nci_fw_send(struct nxp_nci_info *info) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci struct nxp_nci_fw_info *fw_info = &info->fw_info; 13162306a36Sopenharmony_ci long completion_rc; 13262306a36Sopenharmony_ci int r; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci reinit_completion(&fw_info->cmd_completion); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci if (fw_info->written == 0) { 13762306a36Sopenharmony_ci fw_info->frame_size = get_unaligned_be16(fw_info->data) & 13862306a36Sopenharmony_ci NXP_NCI_FW_FRAME_LEN_MASK; 13962306a36Sopenharmony_ci fw_info->data += NXP_NCI_FW_HDR_LEN; 14062306a36Sopenharmony_ci fw_info->size -= NXP_NCI_FW_HDR_LEN; 14162306a36Sopenharmony_ci } 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci if (fw_info->frame_size > fw_info->size) 14462306a36Sopenharmony_ci return -EMSGSIZE; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci r = nxp_nci_fw_send_chunk(info); 14762306a36Sopenharmony_ci if (r < 0) 14862306a36Sopenharmony_ci return r; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci fw_info->written += r; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci if (*fw_info->data == NXP_NCI_FW_CMD_RESET) { 15362306a36Sopenharmony_ci fw_info->cmd_result = 0; 15462306a36Sopenharmony_ci if (fw_info->fw) 15562306a36Sopenharmony_ci schedule_work(&fw_info->work); 15662306a36Sopenharmony_ci } else { 15762306a36Sopenharmony_ci completion_rc = wait_for_completion_interruptible_timeout( 15862306a36Sopenharmony_ci &fw_info->cmd_completion, NXP_NCI_FW_ANSWER_TIMEOUT); 15962306a36Sopenharmony_ci if (completion_rc == 0) 16062306a36Sopenharmony_ci return -ETIMEDOUT; 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci return 0; 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_civoid nxp_nci_fw_work(struct work_struct *work) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct nxp_nci_info *info; 16962306a36Sopenharmony_ci struct nxp_nci_fw_info *fw_info; 17062306a36Sopenharmony_ci int r; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci fw_info = container_of(work, struct nxp_nci_fw_info, work); 17362306a36Sopenharmony_ci info = container_of(fw_info, struct nxp_nci_info, fw_info); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci mutex_lock(&info->info_lock); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci r = fw_info->cmd_result; 17862306a36Sopenharmony_ci if (r < 0) 17962306a36Sopenharmony_ci goto exit_work; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (fw_info->written == fw_info->frame_size) { 18262306a36Sopenharmony_ci fw_info->data += fw_info->frame_size; 18362306a36Sopenharmony_ci fw_info->size -= fw_info->frame_size; 18462306a36Sopenharmony_ci fw_info->written = 0; 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci if (fw_info->size > 0) 18862306a36Sopenharmony_ci r = nxp_nci_fw_send(info); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ciexit_work: 19162306a36Sopenharmony_ci if (r < 0 || fw_info->size == 0) 19262306a36Sopenharmony_ci nxp_nci_fw_work_complete(info, r); 19362306a36Sopenharmony_ci mutex_unlock(&info->info_lock); 19462306a36Sopenharmony_ci} 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ciint nxp_nci_fw_download(struct nci_dev *ndev, const char *firmware_name) 19762306a36Sopenharmony_ci{ 19862306a36Sopenharmony_ci struct nxp_nci_info *info = nci_get_drvdata(ndev); 19962306a36Sopenharmony_ci struct nxp_nci_fw_info *fw_info = &info->fw_info; 20062306a36Sopenharmony_ci int r; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci mutex_lock(&info->info_lock); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci if (!info->phy_ops->set_mode || !info->phy_ops->write) { 20562306a36Sopenharmony_ci r = -ENOTSUPP; 20662306a36Sopenharmony_ci goto fw_download_exit; 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci if (!firmware_name || firmware_name[0] == '\0') { 21062306a36Sopenharmony_ci r = -EINVAL; 21162306a36Sopenharmony_ci goto fw_download_exit; 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci strcpy(fw_info->name, firmware_name); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci r = request_firmware(&fw_info->fw, firmware_name, 21762306a36Sopenharmony_ci ndev->nfc_dev->dev.parent); 21862306a36Sopenharmony_ci if (r < 0) 21962306a36Sopenharmony_ci goto fw_download_exit; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_FW); 22262306a36Sopenharmony_ci if (r < 0) { 22362306a36Sopenharmony_ci release_firmware(fw_info->fw); 22462306a36Sopenharmony_ci goto fw_download_exit; 22562306a36Sopenharmony_ci } 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci info->mode = NXP_NCI_MODE_FW; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci fw_info->data = fw_info->fw->data; 23062306a36Sopenharmony_ci fw_info->size = fw_info->fw->size; 23162306a36Sopenharmony_ci fw_info->written = 0; 23262306a36Sopenharmony_ci fw_info->frame_size = 0; 23362306a36Sopenharmony_ci fw_info->cmd_result = 0; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci schedule_work(&fw_info->work); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cifw_download_exit: 23862306a36Sopenharmony_ci mutex_unlock(&info->info_lock); 23962306a36Sopenharmony_ci return r; 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_cistatic int nxp_nci_fw_read_status(u8 stat) 24362306a36Sopenharmony_ci{ 24462306a36Sopenharmony_ci switch (stat) { 24562306a36Sopenharmony_ci case NXP_NCI_FW_RESULT_OK: 24662306a36Sopenharmony_ci return 0; 24762306a36Sopenharmony_ci case NXP_NCI_FW_RESULT_INVALID_ADDR: 24862306a36Sopenharmony_ci return -EINVAL; 24962306a36Sopenharmony_ci case NXP_NCI_FW_RESULT_UNKNOWN_CMD: 25062306a36Sopenharmony_ci return -EINVAL; 25162306a36Sopenharmony_ci case NXP_NCI_FW_RESULT_ABORTED_CMD: 25262306a36Sopenharmony_ci return -EMSGSIZE; 25362306a36Sopenharmony_ci case NXP_NCI_FW_RESULT_ADDR_RANGE_OFL_ERROR: 25462306a36Sopenharmony_ci return -EADDRNOTAVAIL; 25562306a36Sopenharmony_ci case NXP_NCI_FW_RESULT_BUFFER_OFL_ERROR: 25662306a36Sopenharmony_ci return -ENOBUFS; 25762306a36Sopenharmony_ci case NXP_NCI_FW_RESULT_MEM_BSY: 25862306a36Sopenharmony_ci return -ENOKEY; 25962306a36Sopenharmony_ci case NXP_NCI_FW_RESULT_SIGNATURE_ERROR: 26062306a36Sopenharmony_ci return -EKEYREJECTED; 26162306a36Sopenharmony_ci case NXP_NCI_FW_RESULT_FIRMWARE_VERSION_ERROR: 26262306a36Sopenharmony_ci return -EALREADY; 26362306a36Sopenharmony_ci case NXP_NCI_FW_RESULT_PROTOCOL_ERROR: 26462306a36Sopenharmony_ci return -EPROTO; 26562306a36Sopenharmony_ci case NXP_NCI_FW_RESULT_SFWU_DEGRADED: 26662306a36Sopenharmony_ci return -EHWPOISON; 26762306a36Sopenharmony_ci case NXP_NCI_FW_RESULT_PH_STATUS_FIRST_CHUNK: 26862306a36Sopenharmony_ci return 0; 26962306a36Sopenharmony_ci case NXP_NCI_FW_RESULT_PH_STATUS_NEXT_CHUNK: 27062306a36Sopenharmony_ci return 0; 27162306a36Sopenharmony_ci case NXP_NCI_FW_RESULT_PH_STATUS_INTERNAL_ERROR_5: 27262306a36Sopenharmony_ci return -EINVAL; 27362306a36Sopenharmony_ci default: 27462306a36Sopenharmony_ci return -EIO; 27562306a36Sopenharmony_ci } 27662306a36Sopenharmony_ci} 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_cistatic u16 nxp_nci_fw_check_crc(struct sk_buff *skb) 27962306a36Sopenharmony_ci{ 28062306a36Sopenharmony_ci u16 crc, frame_crc; 28162306a36Sopenharmony_ci size_t len = skb->len - NXP_NCI_FW_CRC_LEN; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci crc = nxp_nci_fw_crc(skb->data, len); 28462306a36Sopenharmony_ci frame_crc = get_unaligned_be16(skb->data + len); 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci return (crc ^ frame_crc); 28762306a36Sopenharmony_ci} 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_civoid nxp_nci_fw_recv_frame(struct nci_dev *ndev, struct sk_buff *skb) 29062306a36Sopenharmony_ci{ 29162306a36Sopenharmony_ci struct nxp_nci_info *info = nci_get_drvdata(ndev); 29262306a36Sopenharmony_ci struct nxp_nci_fw_info *fw_info = &info->fw_info; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci complete(&fw_info->cmd_completion); 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci if (skb) { 29762306a36Sopenharmony_ci if (nxp_nci_fw_check_crc(skb) != 0x00) 29862306a36Sopenharmony_ci fw_info->cmd_result = -EBADMSG; 29962306a36Sopenharmony_ci else 30062306a36Sopenharmony_ci fw_info->cmd_result = nxp_nci_fw_read_status(*(u8 *)skb_pull(skb, NXP_NCI_FW_HDR_LEN)); 30162306a36Sopenharmony_ci kfree_skb(skb); 30262306a36Sopenharmony_ci } else { 30362306a36Sopenharmony_ci fw_info->cmd_result = -EIO; 30462306a36Sopenharmony_ci } 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci if (fw_info->fw) 30762306a36Sopenharmony_ci schedule_work(&fw_info->work); 30862306a36Sopenharmony_ci} 30962306a36Sopenharmony_ciEXPORT_SYMBOL(nxp_nci_fw_recv_frame); 310