NFC: nxp-nci: Add support for NXP NCI chips

Add support for NXP NCI NFC controllers such as the NPC100 or PN7150
families.

Signed-off-by: Clément Perrochaud <clement.perrochaud@effinnov.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
Clément Perrochaud 2015-03-09 11:12:04 +01:00 committed by Samuel Ortiz
parent 25af01ed18
commit dece45855a
9 changed files with 657 additions and 0 deletions

View File

@ -6937,6 +6937,13 @@ S: Supported
F: drivers/block/nvme*
F: include/linux/nvme.h
NXP-NCI NFC DRIVER
M: Clément Perrochaud <clement.perrochaud@effinnov.com>
R: Charles Gorand <charles.gorand@effinnov.com>
L: linux-nfc@lists.01.org (moderated for non-subscribers)
S: Supported
F: drivers/nfc/nxp-nci
NXP TDA998X DRM DRIVER
M: Russell King <rmk+kernel@arm.linux.org.uk>
S: Supported

View File

@ -73,4 +73,5 @@ source "drivers/nfc/microread/Kconfig"
source "drivers/nfc/nfcmrvl/Kconfig"
source "drivers/nfc/st21nfca/Kconfig"
source "drivers/nfc/st21nfcb/Kconfig"
source "drivers/nfc/nxp-nci/Kconfig"
endmenu

View File

@ -13,5 +13,6 @@ obj-$(CONFIG_NFC_MRVL) += nfcmrvl/
obj-$(CONFIG_NFC_TRF7970A) += trf7970a.o
obj-$(CONFIG_NFC_ST21NFCA) += st21nfca/
obj-$(CONFIG_NFC_ST21NFCB) += st21nfcb/
obj-$(CONFIG_NFC_NXP_NCI) += nxp-nci/
ccflags-$(CONFIG_NFC_DEBUG) := -DDEBUG

View File

@ -0,0 +1,13 @@
config NFC_NXP_NCI
tristate "NXP-NCI NFC driver"
depends on NFC_NCI
default n
---help---
Generic core driver for NXP NCI chips such as the NPC100
or PN7150 families.
This is a driver based on the NCI NFC kernel layers and
will thus not work with NXP libnfc library.
To compile this driver as a module, choose m here. The module will
be called nxp_nci.
Say N if unsure.

View File

@ -0,0 +1,9 @@
#
# Makefile for NXP-NCI NFC driver
#
nxp-nci-objs = core.o firmware.o
obj-$(CONFIG_NFC_NXP_NCI) += nxp-nci.o
ccflags-$(CONFIG_NFC_DEBUG) := -DDEBUG

186
drivers/nfc/nxp-nci/core.c Normal file
View File

@ -0,0 +1,186 @@
/*
* 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/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_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 struct nci_ops nxp_nci_ops = {
.open = nxp_nci_open,
.close = nxp_nci_close,
.send = nxp_nci_send,
.fw_download = nxp_nci_fw_download,
};
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);
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>");

View File

@ -0,0 +1,324 @@
/*
* Generic driver for NXP NCI NFC chips
*
* Copyright (C) 2014 NXP Semiconductors All rights reserved.
*
* Author: 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/firmware.h>
#include <linux/nfc.h>
#include <linux/unaligned/access_ok.h>
#include "nxp-nci.h"
/* Crypto operations can take up to 30 seconds */
#define NXP_NCI_FW_ANSWER_TIMEOUT msecs_to_jiffies(30000)
#define NXP_NCI_FW_CMD_RESET 0xF0
#define NXP_NCI_FW_CMD_GETVERSION 0xF1
#define NXP_NCI_FW_CMD_CHECKINTEGRITY 0xE0
#define NXP_NCI_FW_CMD_WRITE 0xC0
#define NXP_NCI_FW_CMD_READ 0xA2
#define NXP_NCI_FW_CMD_GETSESSIONSTATE 0xF2
#define NXP_NCI_FW_CMD_LOG 0xA7
#define NXP_NCI_FW_CMD_FORCE 0xD0
#define NXP_NCI_FW_CMD_GET_DIE_ID 0xF4
#define NXP_NCI_FW_CHUNK_FLAG 0x0400
#define NXP_NCI_FW_RESULT_OK 0x00
#define NXP_NCI_FW_RESULT_INVALID_ADDR 0x01
#define NXP_NCI_FW_RESULT_GENERIC_ERROR 0x02
#define NXP_NCI_FW_RESULT_UNKNOWN_CMD 0x0B
#define NXP_NCI_FW_RESULT_ABORTED_CMD 0x0C
#define NXP_NCI_FW_RESULT_PLL_ERROR 0x0D
#define NXP_NCI_FW_RESULT_ADDR_RANGE_OFL_ERROR 0x1E
#define NXP_NCI_FW_RESULT_BUFFER_OFL_ERROR 0x1F
#define NXP_NCI_FW_RESULT_MEM_BSY 0x20
#define NXP_NCI_FW_RESULT_SIGNATURE_ERROR 0x21
#define NXP_NCI_FW_RESULT_FIRMWARE_VERSION_ERROR 0x24
#define NXP_NCI_FW_RESULT_PROTOCOL_ERROR 0x28
#define NXP_NCI_FW_RESULT_SFWU_DEGRADED 0x2A
#define NXP_NCI_FW_RESULT_PH_STATUS_FIRST_CHUNK 0x2D
#define NXP_NCI_FW_RESULT_PH_STATUS_NEXT_CHUNK 0x2E
#define NXP_NCI_FW_RESULT_PH_STATUS_INTERNAL_ERROR_5 0xC5
void nxp_nci_fw_work_complete(struct nxp_nci_info *info, int result)
{
struct nxp_nci_fw_info *fw_info = &info->fw_info;
int r;
if (info->phy_ops->set_mode) {
r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD);
if (r < 0 && result == 0)
result = -r;
}
info->mode = NXP_NCI_MODE_COLD;
if (fw_info->fw) {
release_firmware(fw_info->fw);
fw_info->fw = NULL;
}
nfc_fw_download_done(info->ndev->nfc_dev, fw_info->name, (u32) -result);
}
/* crc_ccitt cannot be used since it is computed MSB first and not LSB first */
static u16 nxp_nci_fw_crc(u8 const *buffer, size_t len)
{
u16 crc = 0xffff;
while (len--) {
crc = ((crc >> 8) | (crc << 8)) ^ *buffer++;
crc ^= (crc & 0xff) >> 4;
crc ^= (crc & 0xff) << 12;
crc ^= (crc & 0xff) << 5;
}
return crc;
}
static int nxp_nci_fw_send_chunk(struct nxp_nci_info *info)
{
struct nxp_nci_fw_info *fw_info = &info->fw_info;
u16 header, crc;
struct sk_buff *skb;
size_t chunk_len;
size_t remaining_len;
int r;
skb = nci_skb_alloc(info->ndev, info->max_payload, GFP_KERNEL);
if (!skb) {
r = -ENOMEM;
goto chunk_exit;
}
chunk_len = info->max_payload - NXP_NCI_FW_HDR_LEN - NXP_NCI_FW_CRC_LEN;
remaining_len = fw_info->frame_size - fw_info->written;
if (remaining_len > chunk_len) {
header = NXP_NCI_FW_CHUNK_FLAG;
} else {
chunk_len = remaining_len;
header = 0x0000;
}
header |= chunk_len & NXP_NCI_FW_FRAME_LEN_MASK;
put_unaligned_be16(header, skb_put(skb, NXP_NCI_FW_HDR_LEN));
memcpy(skb_put(skb, chunk_len), fw_info->data + fw_info->written,
chunk_len);
crc = nxp_nci_fw_crc(skb->data, chunk_len + NXP_NCI_FW_HDR_LEN);
put_unaligned_be16(crc, skb_put(skb, NXP_NCI_FW_CRC_LEN));
r = info->phy_ops->write(info->phy_id, skb);
if (r >= 0)
r = chunk_len;
kfree_skb(skb);
chunk_exit:
return r;
}
static int nxp_nci_fw_send(struct nxp_nci_info *info)
{
struct nxp_nci_fw_info *fw_info = &info->fw_info;
long completion_rc;
int r;
reinit_completion(&fw_info->cmd_completion);
if (fw_info->written == 0) {
fw_info->frame_size = get_unaligned_be16(fw_info->data) &
NXP_NCI_FW_FRAME_LEN_MASK;
fw_info->data += NXP_NCI_FW_HDR_LEN;
fw_info->size -= NXP_NCI_FW_HDR_LEN;
}
if (fw_info->frame_size > fw_info->size)
return -EMSGSIZE;
r = nxp_nci_fw_send_chunk(info);
if (r < 0)
return r;
fw_info->written += r;
if (*fw_info->data == NXP_NCI_FW_CMD_RESET) {
fw_info->cmd_result = 0;
if (fw_info->fw)
schedule_work(&fw_info->work);
} else {
completion_rc = wait_for_completion_interruptible_timeout(
&fw_info->cmd_completion, NXP_NCI_FW_ANSWER_TIMEOUT);
if (completion_rc == 0)
return -ETIMEDOUT;
}
return 0;
}
void nxp_nci_fw_work(struct work_struct *work)
{
struct nxp_nci_info *info;
struct nxp_nci_fw_info *fw_info;
int r;
fw_info = container_of(work, struct nxp_nci_fw_info, work);
info = container_of(fw_info, struct nxp_nci_info, fw_info);
mutex_lock(&info->info_lock);
r = fw_info->cmd_result;
if (r < 0)
goto exit_work;
if (fw_info->written == fw_info->frame_size) {
fw_info->data += fw_info->frame_size;
fw_info->size -= fw_info->frame_size;
fw_info->written = 0;
}
if (fw_info->size > 0)
r = nxp_nci_fw_send(info);
exit_work:
if (r < 0 || fw_info->size == 0)
nxp_nci_fw_work_complete(info, r);
mutex_unlock(&info->info_lock);
}
int nxp_nci_fw_download(struct nci_dev *ndev, const char *firmware_name)
{
struct nxp_nci_info *info = nci_get_drvdata(ndev);
struct nxp_nci_fw_info *fw_info = &info->fw_info;
int r;
mutex_lock(&info->info_lock);
if (!info->phy_ops->set_mode || !info->phy_ops->write) {
r = -ENOTSUPP;
goto fw_download_exit;
}
if (!firmware_name || firmware_name[0] == '\0') {
r = -EINVAL;
goto fw_download_exit;
}
strcpy(fw_info->name, firmware_name);
r = request_firmware(&fw_info->fw, firmware_name,
ndev->nfc_dev->dev.parent);
if (r < 0)
goto fw_download_exit;
r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_FW);
if (r < 0)
goto fw_download_exit;
info->mode = NXP_NCI_MODE_FW;
fw_info->data = fw_info->fw->data;
fw_info->size = fw_info->fw->size;
fw_info->written = 0;
fw_info->frame_size = 0;
fw_info->cmd_result = 0;
if (fw_info->fw)
schedule_work(&fw_info->work);
fw_download_exit:
mutex_unlock(&info->info_lock);
return r;
}
static int nxp_nci_fw_read_status(u8 stat)
{
switch (stat) {
case NXP_NCI_FW_RESULT_OK:
return 0;
case NXP_NCI_FW_RESULT_INVALID_ADDR:
return -EINVAL;
case NXP_NCI_FW_RESULT_UNKNOWN_CMD:
return -EINVAL;
case NXP_NCI_FW_RESULT_ABORTED_CMD:
return -EMSGSIZE;
case NXP_NCI_FW_RESULT_ADDR_RANGE_OFL_ERROR:
return -EADDRNOTAVAIL;
case NXP_NCI_FW_RESULT_BUFFER_OFL_ERROR:
return -ENOBUFS;
case NXP_NCI_FW_RESULT_MEM_BSY:
return -ENOKEY;
case NXP_NCI_FW_RESULT_SIGNATURE_ERROR:
return -EKEYREJECTED;
case NXP_NCI_FW_RESULT_FIRMWARE_VERSION_ERROR:
return -EALREADY;
case NXP_NCI_FW_RESULT_PROTOCOL_ERROR:
return -EPROTO;
case NXP_NCI_FW_RESULT_SFWU_DEGRADED:
return -EHWPOISON;
case NXP_NCI_FW_RESULT_PH_STATUS_FIRST_CHUNK:
return 0;
case NXP_NCI_FW_RESULT_PH_STATUS_NEXT_CHUNK:
return 0;
case NXP_NCI_FW_RESULT_PH_STATUS_INTERNAL_ERROR_5:
return -EINVAL;
default:
return -EIO;
}
}
static u16 nxp_nci_fw_check_crc(struct sk_buff *skb)
{
u16 crc, frame_crc;
size_t len = skb->len - NXP_NCI_FW_CRC_LEN;
crc = nxp_nci_fw_crc(skb->data, len);
frame_crc = get_unaligned_be16(skb->data + len);
return (crc ^ frame_crc);
}
void nxp_nci_fw_recv_frame(struct nci_dev *ndev, struct sk_buff *skb)
{
struct nxp_nci_info *info = nci_get_drvdata(ndev);
struct nxp_nci_fw_info *fw_info = &info->fw_info;
complete(&fw_info->cmd_completion);
if (skb) {
if (nxp_nci_fw_check_crc(skb) != 0x00)
fw_info->cmd_result = -EBADMSG;
else
fw_info->cmd_result = nxp_nci_fw_read_status(
*skb_pull(skb, NXP_NCI_FW_HDR_LEN));
kfree_skb(skb);
} else {
fw_info->cmd_result = -EIO;
}
if (fw_info->fw)
schedule_work(&fw_info->work);
}
EXPORT_SYMBOL(nxp_nci_fw_recv_frame);

View File

@ -0,0 +1,89 @@
/*
* 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/>.
*/
#ifndef __LOCAL_NXP_NCI_H_
#define __LOCAL_NXP_NCI_H_
#include <linux/completion.h>
#include <linux/firmware.h>
#include <linux/nfc.h>
#include <linux/platform_data/nxp-nci.h>
#include <net/nfc/nci_core.h>
#define NXP_NCI_FW_HDR_LEN 2
#define NXP_NCI_FW_CRC_LEN 2
#define NXP_NCI_FW_FRAME_LEN_MASK 0x03FF
enum nxp_nci_mode {
NXP_NCI_MODE_COLD,
NXP_NCI_MODE_NCI,
NXP_NCI_MODE_FW
};
struct nxp_nci_phy_ops {
int (*set_mode)(void *id, enum nxp_nci_mode mode);
int (*write)(void *id, struct sk_buff *skb);
};
struct nxp_nci_fw_info {
char name[NFC_FIRMWARE_NAME_MAXSIZE + 1];
const struct firmware *fw;
size_t size;
size_t written;
const u8 *data;
size_t frame_size;
struct work_struct work;
struct completion cmd_completion;
int cmd_result;
};
struct nxp_nci_info {
struct nci_dev *ndev;
void *phy_id;
struct device *pdev;
enum nxp_nci_mode mode;
struct nxp_nci_phy_ops *phy_ops;
unsigned int max_payload;
struct mutex info_lock;
struct nxp_nci_fw_info fw_info;
};
int nxp_nci_fw_download(struct nci_dev *ndev, const char *firmware_name);
void nxp_nci_fw_work(struct work_struct *work);
void nxp_nci_fw_recv_frame(struct nci_dev *ndev, struct sk_buff *skb);
void nxp_nci_fw_work_complete(struct nxp_nci_info *info, int result);
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);
void nxp_nci_remove(struct nci_dev *ndev);
#endif /* __LOCAL_NXP_NCI_H_ */

View File

@ -0,0 +1,27 @@
/*
* Generic platform data for the NXP NCI NFC chips.
*
* Copyright (C) 2014 NXP Semiconductors All rights reserved.
*
* Authors: Clément Perrochaud <clement.perrochaud@nxp.com>
*
* 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.
*/
#ifndef _NXP_NCI_H_
#define _NXP_NCI_H_
struct nxp_nci_nfc_platform_data {
unsigned int gpio_en;
unsigned int gpio_fw;
unsigned int irq;
};
#endif /* _NXP_NCI_H_ */