blob: 7c4736ef173ccb13115a11a6725207c6eb28d1f0 [file] [log] [blame]
/*
* Generic driver for NXP NCI NFC chips
*
* Copyright (C) 2014 NXP Semiconductors All rights reserved.
*
* Authors: Clément Perrochaud <clement.perrochaud@nxp.com>
*
* Derived from PN544 device driver:
* Copyright (C) 2012 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions 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 <http://www.gnu.org/licenses/>.
*/
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/nfc.h>
#include <linux/platform_data/nxp-nci.h>
#include <net/nfc/nci_core.h>
#include "nxp-nci.h"
#define NXP_NCI_HDR_LEN 4
#define NXP_NCI_NFC_PROTOCOLS (NFC_PROTO_JEWEL_MASK | \
NFC_PROTO_MIFARE_MASK | \
NFC_PROTO_FELICA_MASK | \
NFC_PROTO_ISO14443_MASK | \
NFC_PROTO_ISO14443_B_MASK | \
NFC_PROTO_ISO15693_MASK | \
NFC_PROTO_NFC_DEP_MASK)
static int nxp_nci_open(struct nci_dev *ndev)
{
struct nxp_nci_info *info = nci_get_drvdata(ndev);
int r = 0;
mutex_lock(&info->info_lock);
if (info->mode != NXP_NCI_MODE_COLD) {
r = -EBUSY;
goto open_exit;
}
if (info->phy_ops->set_mode)
r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_NCI);
info->mode = NXP_NCI_MODE_NCI;
open_exit:
mutex_unlock(&info->info_lock);
return r;
}
static int nxp_nci_close(struct nci_dev *ndev)
{
struct nxp_nci_info *info = nci_get_drvdata(ndev);
int r = 0;
mutex_lock(&info->info_lock);
if (info->phy_ops->set_mode)
r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD);
info->mode = NXP_NCI_MODE_COLD;
mutex_unlock(&info->info_lock);
return r;
}
static int nxp_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
{
struct nxp_nci_info *info = nci_get_drvdata(ndev);
int r;
if (!info->phy_ops->write) {
r = -ENOTSUPP;
goto send_exit;
}
if (info->mode != NXP_NCI_MODE_NCI) {
r = -EINVAL;
goto send_exit;
}
r = info->phy_ops->write(info->phy_id, skb);
if (r < 0)
kfree_skb(skb);
send_exit:
return r;
}
static int nxp_nci_recv_proprietary_rsp_packet(struct nci_dev *ndev, u16 opcode,
struct sk_buff *skb)
{
u32 subcmd;
switch (opcode) {
case NCI_OP_CORE_SET_CONFIG_RSP:
if (skb->len > 0 && skb->data[0] == 0xA0)
subcmd = NXP_VENDOR_SUBCMD_SET_PROPRIETARY_CONFIG;
else
subcmd = NXP_VENDOR_SUBCMD_SET_NCI_CONFIG;
break;
case NCI_OP_CORE_GET_CONFIG_RSP:
if (skb->len > 0 && skb->data[0] == 0xA0)
subcmd = NXP_VENDOR_SUBCMD_GET_PROPRIETARY_CONFIG;
else
subcmd = NXP_VENDOR_SUBCMD_GET_NCI_CONFIG;
break;
case NXP_VENDOR_RSP_PROPRIETARY_EXTENSIONS:
subcmd = NXP_VENDOR_SUBCMD_ENABLE_PROPRIETARY_EXTENSIONS;
break;
case NXP_VENDOR_RSP_ANTENNA_SELFTEST:
subcmd = NXP_VENDOR_SUBCMD_ANTENNA_SELFTEST;
break;
default:
return -ENOSYS;
}
return nfc_vendor_rsp(ndev->nfc_dev, NXP_VENDOR_ID, subcmd, skb->len,
skb->data);
}
static struct nci_ops nxp_nci_ops = {
.open = nxp_nci_open,
.close = nxp_nci_close,
.send = nxp_nci_send,
.fw_download = nxp_nci_fw_download,
.recv_proprietary_rsp_packet = nxp_nci_recv_proprietary_rsp_packet,
};
static int nxp_nci_proprietary_cmd(struct nfc_dev *dev, u16 cmd, void *data,
size_t data_len)
{
struct nci_dev *ndev = nfc_get_drvdata(dev);
struct sk_buff *skb;
struct nci_ctrl_hdr *hdr;
long completion_rc;
int rc = 0;
ndev->req_status = NCI_REQ_PEND;
init_completion(&ndev->req_completion);
skb = nci_skb_alloc(ndev,
(NCI_CTRL_HDR_SIZE + data_len),
GFP_KERNEL);
if (!skb) {
pr_err("no memory for command\n");
return -ENOMEM;
}
hdr = (struct nci_ctrl_hdr *) skb_put(skb, NCI_CTRL_HDR_SIZE);
hdr->gid = nci_opcode_gid(cmd);
hdr->oid = nci_opcode_oid(cmd);
hdr->plen = data_len;
nci_mt_set((__u8 *)hdr, NCI_MT_CMD_PKT);
nci_pbf_set((__u8 *)hdr, NCI_PBF_LAST);
if (data_len > 0)
memcpy(skb_put(skb, data_len), data, data_len);
skb_queue_tail(&ndev->cmd_q, skb);
queue_work(ndev->cmd_wq, &ndev->cmd_work);
completion_rc = wait_for_completion_interruptible_timeout(
&ndev->req_completion,
msecs_to_jiffies(NCI_SET_CONFIG_TIMEOUT));
pr_debug("wait_for_completion return %ld\n", completion_rc);
if (completion_rc > 0) {
switch (ndev->req_status) {
case NCI_REQ_DONE:
rc = nci_to_errno(ndev->req_result);
break;
case NCI_REQ_CANCELED:
rc = -ndev->req_result;
break;
default:
rc = -ETIMEDOUT;
break;
}
} else {
pr_err("wait_for_completion_interruptible_timeout failed %ld\n",
completion_rc);
rc = ((completion_rc == 0) ? (-ETIMEDOUT) : (completion_rc));
}
ndev->req_status = ndev->req_result = 0;
return rc;
}
static int nxp_nci_set_nci_config(struct nfc_dev *dev, void *data,
size_t data_len)
{
unsigned char i, fields;
unsigned int data_index = 1;
unsigned char* param = (unsigned char*) data;
struct nxp_set_nci_config_param_hdr* param_hdr;
int r;
if (data_len < 1)
return -EINVAL;
fields = param[0];
for (i = 0; i < fields; i++) {
param_hdr = (struct nxp_set_nci_config_param_hdr*)
&param[data_index];
data_index +=
sizeof(struct nxp_set_nci_config_param_hdr);
if (data_index >= data_len)
return -EINVAL;
data_index += param_hdr->len;
}
if (data_index != data_len)
return -EINVAL;
r = nxp_nci_proprietary_cmd(dev, NCI_OP_CORE_SET_CONFIG_CMD,
data, data_len);
nfc_vendor_rsp(dev, NXP_VENDOR_ID,
NXP_VENDOR_SUBCMD_SET_NCI_CONFIG, 0, NULL);
return r;
}
static int nxp_nci_set_proprietary_config(struct nfc_dev *dev, void *data,
size_t data_len)
{
unsigned char i, fields;
unsigned int data_index = 1;
unsigned char* param = (unsigned char*) data;
struct nxp_set_proprietary_config_param_hdr* param_hdr;
int r;
if (data_len < 1)
return -EINVAL;
fields = param[0];
for (i = 0; i < fields; i++) {
param_hdr = (struct nxp_set_proprietary_config_param_hdr*)
&param[data_index];
data_index +=
sizeof(struct nxp_set_proprietary_config_param_hdr);
if (data_index >= data_len)
return -EINVAL;
data_index += param_hdr->len;
}
if (data_index != data_len)
return -EINVAL;
r = nxp_nci_proprietary_cmd(dev, NCI_OP_CORE_SET_CONFIG_CMD,
data, data_len);
nfc_vendor_rsp(dev, NXP_VENDOR_ID,
NXP_VENDOR_SUBCMD_SET_PROPRIETARY_CONFIG, 0, NULL);
return r;
}
static int nxp_nci_get_nci_config(struct nfc_dev *dev, void *data,
size_t data_len)
{
unsigned char fields;
unsigned int data_index = 1;
unsigned char* param = (unsigned char*) data;
if (data_len < 1)
return -EINVAL;
fields = param[0];
data_index += fields * sizeof(nxp_get_nci_config_param_hdr);
if (data_index != data_len)
return -EINVAL;
return nxp_nci_proprietary_cmd(dev, NCI_OP_CORE_GET_CONFIG_CMD,
data, data_len);
}
static int nxp_nci_get_proprietary_config(struct nfc_dev *dev, void *data,
size_t data_len)
{
unsigned char fields;
unsigned int data_index = 1;
unsigned char* param = (unsigned char*) data;
if (data_len < 1)
return -EINVAL;
fields = param[0];
data_index += fields * sizeof(nxp_get_proprietary_config_param_hdr);
if (data_index != data_len)
return -EINVAL;
return nxp_nci_proprietary_cmd(dev, NCI_OP_CORE_GET_CONFIG_CMD,
data, data_len);
}
static int nxp_nci_enable_proprietary_extensions(struct nfc_dev *dev,
void *data, size_t data_len)
{
if (data_len != 0)
return -EINVAL;
return nxp_nci_proprietary_cmd(dev,
NCI_OP_PROPRIETARY_NXP_ENABLE_PROPRIETARY_EXTENSIONS_CMD,
NULL, 0);
}
static int nxp_nci_antenna_selftest(struct nfc_dev *dev, void *data,
size_t data_len)
{
unsigned char measurement_id;
unsigned char* param = (unsigned char*) data;
unsigned int header_len;
if (data_len < 1)
return -EINVAL;
measurement_id = param[0];
switch (measurement_id) {
case NXP_VENDOR_ANTENNA_SELFTEST_MEASUREMENT_CURRENT:
header_len = 2;
break;
case NXP_VENDOR_ANTENNA_SELFTEST_MEASUREMENT_AGC:
case NXP_VENDOR_ANTENNA_SELFTEST_MEASUREMENT_AGC_FIXED_NFCLD:
case NXP_VENDOR_ANTENNA_SELFTEST_MEASUREMENT_AGC_DIFF:
case NXP_VENDOR_ANTENNA_SELFTEST_MEASUREMENT_RF_FIELD:
header_len = 4;
break;
default:
return -EINVAL;
}
if (data_len != header_len)
return -EINVAL;
return nxp_nci_proprietary_cmd(dev,
NCI_OP_PROPRIETARY_NXP_ANTENNA_SELFTEST_CMD,
data,
data_len);
}
static struct nfc_vendor_cmd nxp_nci_vendor_cmds[] = {
{
.vendor_id = NXP_VENDOR_ID,
.subcmd = NXP_VENDOR_SUBCMD_SET_NCI_CONFIG,
.cmd = nxp_nci_set_nci_config
},
{
.vendor_id = NXP_VENDOR_ID,
.subcmd = NXP_VENDOR_SUBCMD_SET_PROPRIETARY_CONFIG,
.cmd = nxp_nci_set_proprietary_config
},
{
.vendor_id = NXP_VENDOR_ID,
.subcmd = NXP_VENDOR_SUBCMD_GET_NCI_CONFIG,
.cmd = nxp_nci_get_nci_config
},
{
.vendor_id = NXP_VENDOR_ID,
.subcmd = NXP_VENDOR_SUBCMD_GET_PROPRIETARY_CONFIG,
.cmd = nxp_nci_get_proprietary_config
},
{
.vendor_id = NXP_VENDOR_ID,
.subcmd = NXP_VENDOR_SUBCMD_ENABLE_PROPRIETARY_EXTENSIONS,
.cmd = nxp_nci_enable_proprietary_extensions
},
{
.vendor_id = NXP_VENDOR_ID,
.subcmd = NXP_VENDOR_SUBCMD_ANTENNA_SELFTEST,
.cmd = nxp_nci_antenna_selftest
},
};
int nxp_nci_probe(void *phy_id, struct device *pdev,
struct nxp_nci_phy_ops *phy_ops, unsigned int max_payload,
struct nci_dev **ndev)
{
struct nxp_nci_info *info;
int r;
info = devm_kzalloc(pdev, sizeof(struct nxp_nci_info), GFP_KERNEL);
if (!info) {
r = -ENOMEM;
goto probe_exit;
}
info->phy_id = phy_id;
info->pdev = pdev;
info->phy_ops = phy_ops;
info->max_payload = max_payload;
INIT_WORK(&info->fw_info.work, nxp_nci_fw_work);
init_completion(&info->fw_info.cmd_completion);
mutex_init(&info->info_lock);
if (info->phy_ops->set_mode) {
r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD);
if (r < 0)
goto probe_exit;
}
info->mode = NXP_NCI_MODE_COLD;
info->ndev = nci_allocate_device(&nxp_nci_ops, NXP_NCI_NFC_PROTOCOLS,
NXP_NCI_HDR_LEN, 0);
if (!info->ndev) {
r = -ENOMEM;
goto probe_exit;
}
nci_set_parent_dev(info->ndev, pdev);
nci_set_drvdata(info->ndev, info);
nci_set_vendor_cmds(info->ndev, nxp_nci_vendor_cmds,
__NXP_VENDOR_SUBCMD_AFTER_LAST);
r = nci_register_device(info->ndev);
if (r < 0)
goto probe_exit_free_nci;
*ndev = info->ndev;
goto probe_exit;
probe_exit_free_nci:
nci_free_device(info->ndev);
probe_exit:
return r;
}
EXPORT_SYMBOL(nxp_nci_probe);
void nxp_nci_remove(struct nci_dev *ndev)
{
struct nxp_nci_info *info = nci_get_drvdata(ndev);
if (info->mode == NXP_NCI_MODE_FW)
nxp_nci_fw_work_complete(info, -ESHUTDOWN);
cancel_work_sync(&info->fw_info.work);
mutex_lock(&info->info_lock);
if (info->phy_ops->set_mode)
info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD);
nci_unregister_device(ndev);
nci_free_device(ndev);
mutex_unlock(&info->info_lock);
}
EXPORT_SYMBOL(nxp_nci_remove);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("NXP NCI NFC driver");
MODULE_AUTHOR("Clément Perrochaud <clement.perrochaud@nxp.com>");