/* * Texas Instrument's NFC Driver For Shared Transport. * * NFC Driver acts as interface between NCI core and * TI Shared Transport Layer. * * Copyright (C) 2011 Texas Instruments, Inc. * * Written by Ilan Elias * * Acknowledgements: * This file is based on btwilink.c, which was written * by Raja Mani and Pavan Savoy. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * */ #include #include #include #include #include #include #include #include #define NFCWILINK_CHNL 12 #define NFCWILINK_OPCODE 7 #define NFCWILINK_MAX_FRAME_SIZE 300 #define NFCWILINK_HDR_LEN 4 #define NFCWILINK_OFFSET_LEN_IN_HDR 1 #define NFCWILINK_LEN_SIZE 2 #define NFCWILINK_REGISTER_TIMEOUT 8000 /* 8 sec */ #define NFCWILINK_CMD_TIMEOUT 5000 /* 5 sec */ #define BTS_FILE_NAME_MAX_SIZE 40 #define BTS_FILE_HDR_MAGIC 0x42535442 #define BTS_FILE_CMD_MAX_LEN 0xff #define BTS_FILE_ACTION_TYPE_SEND_CMD 1 #define NCI_VS_NFCC_INFO_CMD_GID 0x2f #define NCI_VS_NFCC_INFO_CMD_OID 0x12 #define NCI_VS_NFCC_INFO_RSP_GID 0x4f #define NCI_VS_NFCC_INFO_RSP_OID 0x12 struct nfcwilink_hdr { __u8 chnl; __u8 opcode; __le16 len; } __packed; struct nci_vs_nfcc_info_cmd { __u8 gid; __u8 oid; __u8 plen; } __packed; struct nci_vs_nfcc_info_rsp { __u8 gid; __u8 oid; __u8 plen; __u8 status; __u8 hw_id; __u8 sw_ver_x; __u8 sw_ver_z; __u8 patch_id; } __packed; struct bts_file_hdr { __le32 magic; __le32 ver; __u8 rfu[24]; __u8 actions[0]; } __packed; struct bts_file_action { __le16 type; __le16 len; __u8 data[0]; } __packed; struct nfcwilink { struct platform_device *pdev; struct nci_dev *ndev; unsigned long flags; char st_register_cb_status; long (*st_write) (struct sk_buff *); struct completion completed; struct nci_vs_nfcc_info_rsp nfcc_info; }; /* NFCWILINK driver flags */ enum { NFCWILINK_RUNNING, NFCWILINK_FW_DOWNLOAD, }; static int nfcwilink_send(struct nci_dev *ndev, struct sk_buff *skb); static inline struct sk_buff *nfcwilink_skb_alloc(unsigned int len, gfp_t how) { struct sk_buff *skb; skb = alloc_skb(len + NFCWILINK_HDR_LEN, how); if (skb) skb_reserve(skb, NFCWILINK_HDR_LEN); return skb; } static void nfcwilink_fw_download_receive(struct nfcwilink *drv, struct sk_buff *skb) { struct nci_vs_nfcc_info_rsp *rsp = (void *)skb->data; /* Detect NCI_VS_NFCC_INFO_RSP and store the result */ if ((skb->len > 3) && (rsp->gid == NCI_VS_NFCC_INFO_RSP_GID) && (rsp->oid == NCI_VS_NFCC_INFO_RSP_OID)) { memcpy(&drv->nfcc_info, rsp, sizeof(struct nci_vs_nfcc_info_rsp)); } kfree_skb(skb); complete(&drv->completed); } static int nfcwilink_get_bts_file_name(struct nfcwilink *drv, char *file_name) { struct nci_vs_nfcc_info_cmd *cmd; struct sk_buff *skb; unsigned long comp_ret; int rc; skb = nfcwilink_skb_alloc(sizeof(struct nci_vs_nfcc_info_cmd), GFP_KERNEL); if (!skb) { nfc_err(&drv->pdev->dev, "no memory for nci_vs_nfcc_info_cmd\n"); return -ENOMEM; } cmd = (struct nci_vs_nfcc_info_cmd *) skb_put(skb, sizeof(struct nci_vs_nfcc_info_cmd)); cmd->gid = NCI_VS_NFCC_INFO_CMD_GID; cmd->oid = NCI_VS_NFCC_INFO_CMD_OID; cmd->plen = 0; drv->nfcc_info.plen = 0; rc = nfcwilink_send(drv->ndev, skb); if (rc) return rc; comp_ret = wait_for_completion_timeout(&drv->completed, msecs_to_jiffies(NFCWILINK_CMD_TIMEOUT)); dev_dbg(&drv->pdev->dev, "wait_for_completion_timeout returned %ld\n", comp_ret); if (comp_ret == 0) { nfc_err(&drv->pdev->dev, "timeout on wait_for_completion_timeout\n"); return -ETIMEDOUT; } dev_dbg(&drv->pdev->dev, "nci_vs_nfcc_info_rsp: plen %d, status %d\n", drv->nfcc_info.plen, drv->nfcc_info.status); if ((drv->nfcc_info.plen != 5) || (drv->nfcc_info.status != 0)) { nfc_err(&drv->pdev->dev, "invalid nci_vs_nfcc_info_rsp\n"); return -EINVAL; } snprintf(file_name, BTS_FILE_NAME_MAX_SIZE, "TINfcInit_%d.%d.%d.%d.bts", drv->nfcc_info.hw_id, drv->nfcc_info.sw_ver_x, drv->nfcc_info.sw_ver_z, drv->nfcc_info.patch_id); nfc_info(&drv->pdev->dev, "nfcwilink FW file name: %s\n", file_name); return 0; } static int nfcwilink_send_bts_cmd(struct nfcwilink *drv, __u8 *data, int len) { struct nfcwilink_hdr *hdr = (struct nfcwilink_hdr *)data; struct sk_buff *skb; unsigned long comp_ret; int rc; /* verify valid cmd for the NFC channel */ if ((len <= sizeof(struct nfcwilink_hdr)) || (len > BTS_FILE_CMD_MAX_LEN) || (hdr->chnl != NFCWILINK_CHNL) || (hdr->opcode != NFCWILINK_OPCODE)) { nfc_err(&drv->pdev->dev, "ignoring invalid bts cmd, len %d, chnl %d, opcode %d\n", len, hdr->chnl, hdr->opcode); return 0; } /* remove the ST header */ len -= sizeof(struct nfcwilink_hdr); data += sizeof(struct nfcwilink_hdr); skb = nfcwilink_skb_alloc(len, GFP_KERNEL); if (!skb) { nfc_err(&drv->pdev->dev, "no memory for bts cmd\n"); return -ENOMEM; } memcpy(skb_put(skb, len), data, len); rc = nfcwilink_send(drv->ndev, skb); if (rc) return rc; comp_ret = wait_for_completion_timeout(&drv->completed, msecs_to_jiffies(NFCWILINK_CMD_TIMEOUT)); dev_dbg(&drv->pdev->dev, "wait_for_completion_timeout returned %ld\n", comp_ret); if (comp_ret == 0) { nfc_err(&drv->pdev->dev, "timeout on wait_for_completion_timeout\n"); return -ETIMEDOUT; } return 0; } static int nfcwilink_download_fw(struct nfcwilink *drv) { unsigned char file_name[BTS_FILE_NAME_MAX_SIZE]; const struct firmware *fw; __u16 action_type, action_len; __u8 *ptr; int len, rc; set_bit(NFCWILINK_FW_DOWNLOAD, &drv->flags); rc = nfcwilink_get_bts_file_name(drv, file_name); if (rc) goto exit; rc = request_firmware(&fw, file_name, &drv->pdev->dev); if (rc) { nfc_err(&drv->pdev->dev, "request_firmware failed %d\n", rc); /* if the file is not found, don't exit with failure */ if (rc == -ENOENT) rc = 0; goto exit; } len = fw->size; ptr = (__u8 *)fw->data; if ((len == 0) || (ptr == NULL)) { dev_dbg(&drv->pdev->dev, "request_firmware returned size %d\n", len); goto release_fw; } if (__le32_to_cpu(((struct bts_file_hdr *)ptr)->magic) != BTS_FILE_HDR_MAGIC) { nfc_err(&drv->pdev->dev, "wrong bts magic number\n"); rc = -EINVAL; goto release_fw; } /* remove the BTS header */ len -= sizeof(struct bts_file_hdr); ptr