linux/drivers/nfc/fdp/fdp.c
Robert Dolca a06347c04c NFC: Add Intel Fields Peak NFC solution driver
Fields Peak complies with the ISO/IEC 14443A/B, 15693, 18092,
and JIS X 6319-4. It is an NCI based controller.

RF Protocols supported:
 - NFC Forum Type 1 Tags (Jewel, Topaz)
 - NFC Forum Type 2 Tags (Mifare UL)
 - NFC Forum Type 3 Tags (FeliCa)
 - NFC Forum Type 4A (ISO/IEC 14443 A-4 106kbps to 848kbps)
 - NFC Forum Type 4B (ISO/IEC 14443 B-4 106kbps to 848kbps)
 - NFCIP in passive and active modes (ISO/IEC 18092 106kbps
   to 424kbps)
 - B’ (based on ISO/IEC 14443 B-2)
 - iCLASS (based on ISO/IEC 15693-2)
 - Vicinity cards (ISO/IEC 15693-3)
 - Kovio tags (NFC Forum Type 2)

The device can be enumerated using ACPI using the id INT339A.
The 1st GPIO is the IRQ and the 2nd is the RESET pin.

Signed-off-by: Robert Dolca <robert.dolca@intel.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2015-10-25 20:29:16 +01:00

818 lines
19 KiB
C

/* -------------------------------------------------------------------------
* Copyright (C) 2014-2016, Intel Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
* -------------------------------------------------------------------------
*/
#include <linux/module.h>
#include <linux/nfc.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <net/nfc/nci_core.h>
#include "fdp.h"
#define FDP_OTP_PATCH_NAME "otp.bin"
#define FDP_RAM_PATCH_NAME "ram.bin"
#define FDP_FW_HEADER_SIZE 576
#define FDP_FW_UPDATE_SLEEP 1000
#define NCI_GET_VERSION_TIMEOUT 8000
#define NCI_PATCH_REQUEST_TIMEOUT 8000
#define FDP_PATCH_CONN_DEST 0xC2
#define FDP_PATCH_CONN_PARAM_TYPE 0xA0
#define NCI_PATCH_TYPE_RAM 0x00
#define NCI_PATCH_TYPE_OTP 0x01
#define NCI_PATCH_TYPE_EOT 0xFF
#define NCI_PARAM_ID_FW_RAM_VERSION 0xA0
#define NCI_PARAM_ID_FW_OTP_VERSION 0xA1
#define NCI_PARAM_ID_OTP_LIMITED_VERSION 0xC5
#define NCI_PARAM_ID_KEY_INDEX_ID 0xC6
#define NCI_GID_PROP 0x0F
#define NCI_OP_PROP_PATCH_OID 0x08
#define NCI_OP_PROP_SET_PDATA_OID 0x23
struct fdp_nci_info {
struct nfc_phy_ops *phy_ops;
struct fdp_i2c_phy *phy;
struct nci_dev *ndev;
const struct firmware *otp_patch;
const struct firmware *ram_patch;
u32 otp_patch_version;
u32 ram_patch_version;
u32 otp_version;
u32 ram_version;
u32 limited_otp_version;
u8 key_index;
u8 *fw_vsc_cfg;
u8 clock_type;
u32 clock_freq;
atomic_t data_pkt_counter;
void (*data_pkt_counter_cb)(struct nci_dev *ndev);
u8 setup_patch_sent;
u8 setup_patch_ntf;
u8 setup_patch_status;
u8 setup_reset_ntf;
wait_queue_head_t setup_wq;
};
static u8 nci_core_get_config_otp_ram_version[5] = {
0x04,
NCI_PARAM_ID_FW_RAM_VERSION,
NCI_PARAM_ID_FW_OTP_VERSION,
NCI_PARAM_ID_OTP_LIMITED_VERSION,
NCI_PARAM_ID_KEY_INDEX_ID
};
struct nci_core_get_config_rsp {
u8 status;
u8 count;
u8 data[0];
};
static int fdp_nci_create_conn(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct core_conn_create_dest_spec_params param;
int r;
/* proprietary destination specific paramerer without value */
param.type = FDP_PATCH_CONN_PARAM_TYPE;
param.length = 0x00;
r = nci_core_conn_create(info->ndev, FDP_PATCH_CONN_DEST, 1,
sizeof(param), &param);
if (r)
return r;
return nci_get_conn_info_by_id(ndev, 0);
}
static inline int fdp_nci_get_versions(struct nci_dev *ndev)
{
return nci_core_cmd(ndev, NCI_OP_CORE_GET_CONFIG_CMD,
sizeof(nci_core_get_config_otp_ram_version),
(__u8 *) &nci_core_get_config_otp_ram_version);
}
static inline int fdp_nci_patch_cmd(struct nci_dev *ndev, u8 type)
{
return nci_prop_cmd(ndev, NCI_OP_PROP_PATCH_OID, sizeof(type), &type);
}
static inline int fdp_nci_set_production_data(struct nci_dev *ndev, u8 len,
char *data)
{
return nci_prop_cmd(ndev, NCI_OP_PROP_SET_PDATA_OID, len, data);
}
static int fdp_nci_set_clock(struct nci_dev *ndev, u8 clock_type,
u32 clock_freq)
{
u32 fc = 13560;
u32 nd, num, delta;
char data[9];
nd = (24 * fc) / clock_freq;
delta = 24 * fc - nd * clock_freq;
num = (32768 * delta) / clock_freq;
data[0] = 0x00;
data[1] = 0x00;
data[2] = 0x00;
data[3] = 0x10;
data[4] = 0x04;
data[5] = num & 0xFF;
data[6] = (num >> 8) & 0xff;
data[7] = nd;
data[8] = clock_type;
return fdp_nci_set_production_data(ndev, 9, data);
}
static void fdp_nci_send_patch_cb(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
info->setup_patch_sent = 1;
wake_up(&info->setup_wq);
}
/**
* Register a packet sent counter and a callback
*
* We have no other way of knowing when all firmware packets were sent out
* on the i2c bus. We need to know that in order to close the connection and
* send the patch end message.
*/
static void fdp_nci_set_data_pkt_counter(struct nci_dev *ndev,
void (*cb)(struct nci_dev *ndev), int count)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
dev_dbg(dev, "NCI data pkt counter %d\n", count);
atomic_set(&info->data_pkt_counter, count);
info->data_pkt_counter_cb = cb;
}
/**
* The device is expecting a stream of packets. All packets need to
* have the PBF flag set to 0x0 (last packet) even if the firmware
* file is segmented and there are multiple packets. If we give the
* whole firmware to nci_send_data it will segment it and it will set
* the PBF flag to 0x01 so we need to do the segmentation here.
*
* The firmware will be analyzed and applied when we send NCI_OP_PROP_PATCH_CMD
* command with NCI_PATCH_TYPE_EOT parameter. The device will send a
* NFCC_PATCH_NTF packaet and a NCI_OP_CORE_RESET_NTF packet.
*/
static int fdp_nci_send_patch(struct nci_dev *ndev, u8 conn_id, u8 type)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
const struct firmware *fw;
struct sk_buff *skb;
unsigned long len;
u8 max_size, payload_size;
int rc = 0;
if ((type == NCI_PATCH_TYPE_OTP && !info->otp_patch) ||
(type == NCI_PATCH_TYPE_RAM && !info->ram_patch))
return -EINVAL;
if (type == NCI_PATCH_TYPE_OTP)
fw = info->otp_patch;
else
fw = info->ram_patch;
max_size = nci_conn_max_data_pkt_payload_size(ndev, conn_id);
if (max_size <= 0)
return -EINVAL;
len = fw->size;
fdp_nci_set_data_pkt_counter(ndev, fdp_nci_send_patch_cb,
DIV_ROUND_UP(fw->size, max_size));
while (len) {
payload_size = min_t(unsigned long, (unsigned long) max_size,
len);
skb = nci_skb_alloc(ndev, (NCI_CTRL_HDR_SIZE + payload_size),
GFP_KERNEL);
if (!skb) {
fdp_nci_set_data_pkt_counter(ndev, NULL, 0);
return -ENOMEM;
}
skb_reserve(skb, NCI_CTRL_HDR_SIZE);
memcpy(skb_put(skb, payload_size), fw->data + (fw->size - len),
payload_size);
rc = nci_send_data(ndev, conn_id, skb);
if (rc) {
fdp_nci_set_data_pkt_counter(ndev, NULL, 0);
return rc;
}
len -= payload_size;
}
return rc;
}
static int fdp_nci_open(struct nci_dev *ndev)
{
int r;
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
dev_dbg(dev, "%s\n", __func__);
r = info->phy_ops->enable(info->phy);
return r;
}
static int fdp_nci_close(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
dev_dbg(dev, "%s\n", __func__);
return 0;
}
static int fdp_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
dev_dbg(dev, "%s\n", __func__);
if (atomic_dec_and_test(&info->data_pkt_counter))
info->data_pkt_counter_cb(ndev);
return info->phy_ops->write(info->phy, skb);
}
int fdp_nci_recv_frame(struct nci_dev *ndev, struct sk_buff *skb)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
dev_dbg(dev, "%s\n", __func__);
return nci_recv_frame(ndev, skb);
}
EXPORT_SYMBOL(fdp_nci_recv_frame);
static int fdp_nci_request_firmware(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
u8 *data;
int r;
r = request_firmware(&info->ram_patch, FDP_RAM_PATCH_NAME, dev);
if (r < 0) {
nfc_err(dev, "RAM patch request error\n");
goto error;
}
data = (u8 *) info->ram_patch->data;
info->ram_patch_version =
data[FDP_FW_HEADER_SIZE] |
(data[FDP_FW_HEADER_SIZE + 1] << 8) |
(data[FDP_FW_HEADER_SIZE + 2] << 16) |
(data[FDP_FW_HEADER_SIZE + 3] << 24);
dev_dbg(dev, "RAM patch version: %d, size: %d\n",
info->ram_patch_version, (int) info->ram_patch->size);
r = request_firmware(&info->otp_patch, FDP_OTP_PATCH_NAME, dev);
if (r < 0) {
nfc_err(dev, "OTP patch request error\n");
goto out;
}
data = (u8 *) info->otp_patch->data;
info->otp_patch_version =
data[FDP_FW_HEADER_SIZE] |
(data[FDP_FW_HEADER_SIZE + 1] << 8) |
(data[FDP_FW_HEADER_SIZE+2] << 16) |
(data[FDP_FW_HEADER_SIZE+3] << 24);
dev_dbg(dev, "OTP patch version: %d, size: %d\n",
info->otp_patch_version, (int) info->otp_patch->size);
out:
return 0;
error:
return r;
}
static void fdp_nci_release_firmware(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
if (info->otp_patch) {
release_firmware(info->otp_patch);
info->otp_patch = NULL;
}
if (info->ram_patch) {
release_firmware(info->ram_patch);
info->otp_patch = NULL;
}
}
static int fdp_nci_patch_otp(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
u8 conn_id;
int r = 0;
if (info->otp_version >= info->otp_patch_version)
goto out;
info->setup_patch_sent = 0;
info->setup_reset_ntf = 0;
info->setup_patch_ntf = 0;
/* Patch init request */
r = fdp_nci_patch_cmd(ndev, NCI_PATCH_TYPE_OTP);
if (r)
goto out;
/* Patch data connection creation */
conn_id = fdp_nci_create_conn(ndev);
if (conn_id < 0) {
r = conn_id;
goto out;
}
/* Send the patch over the data connection */
r = fdp_nci_send_patch(ndev, conn_id, NCI_PATCH_TYPE_OTP);
if (r)
goto out;
/* Wait for all the packets to be send over i2c */
wait_event_interruptible(info->setup_wq,
info->setup_patch_sent == 1);
/* make sure that the NFCC processed the last data packet */
msleep(FDP_FW_UPDATE_SLEEP);
/* Close the data connection */
r = nci_core_conn_close(info->ndev, conn_id);
if (r)
goto out;
/* Patch finish message */
if (fdp_nci_patch_cmd(ndev, NCI_PATCH_TYPE_EOT)) {
nfc_err(dev, "OTP patch error 0x%x\n", r);
r = -EINVAL;
goto out;
}
/* If the patch notification didn't arrive yet, wait for it */
wait_event_interruptible(info->setup_wq, info->setup_patch_ntf);
/* Check if the patching was successful */
r = info->setup_patch_status;
if (r) {
nfc_err(dev, "OTP patch error 0x%x\n", r);
r = -EINVAL;
goto out;
}
/*
* We need to wait for the reset notification before we
* can continue
*/
wait_event_interruptible(info->setup_wq, info->setup_reset_ntf);
out:
return r;
}
static int fdp_nci_patch_ram(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
u8 conn_id;
int r = 0;
if (info->ram_version >= info->ram_patch_version)
goto out;
info->setup_patch_sent = 0;
info->setup_reset_ntf = 0;
info->setup_patch_ntf = 0;
/* Patch init request */
r = fdp_nci_patch_cmd(ndev, NCI_PATCH_TYPE_RAM);
if (r)
goto out;
/* Patch data connection creation */
conn_id = fdp_nci_create_conn(ndev);
if (conn_id < 0) {
r = conn_id;
goto out;
}
/* Send the patch over the data connection */
r = fdp_nci_send_patch(ndev, conn_id, NCI_PATCH_TYPE_RAM);
if (r)
goto out;
/* Wait for all the packets to be send over i2c */
wait_event_interruptible(info->setup_wq,
info->setup_patch_sent == 1);
/* make sure that the NFCC processed the last data packet */
msleep(FDP_FW_UPDATE_SLEEP);
/* Close the data connection */
r = nci_core_conn_close(info->ndev, conn_id);
if (r)
goto out;
/* Patch finish message */
if (fdp_nci_patch_cmd(ndev, NCI_PATCH_TYPE_EOT)) {
nfc_err(dev, "RAM patch error 0x%x\n", r);
r = -EINVAL;
goto out;
}
/* If the patch notification didn't arrive yet, wait for it */
wait_event_interruptible(info->setup_wq, info->setup_patch_ntf);
/* Check if the patching was successful */
r = info->setup_patch_status;
if (r) {
nfc_err(dev, "RAM patch error 0x%x\n", r);
r = -EINVAL;
goto out;
}
/*
* We need to wait for the reset notification before we
* can continue
*/
wait_event_interruptible(info->setup_wq, info->setup_reset_ntf);
out:
return r;
}
static int fdp_nci_setup(struct nci_dev *ndev)
{
/* Format: total length followed by an NCI packet */
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
int r;
u8 patched = 0;
dev_dbg(dev, "%s\n", __func__);
r = nci_core_init(ndev);
if (r)
goto error;
/* Get RAM and OTP version */
r = fdp_nci_get_versions(ndev);
if (r)
goto error;
/* Load firmware from disk */
r = fdp_nci_request_firmware(ndev);
if (r)
goto error;
/* Update OTP */
if (info->otp_version < info->otp_patch_version) {
r = fdp_nci_patch_otp(ndev);
if (r)
goto error;
patched = 1;
}
/* Update RAM */
if (info->ram_version < info->ram_patch_version) {
r = fdp_nci_patch_ram(ndev);
if (r)
goto error;
patched = 1;
}
/* Release the firmware buffers */
fdp_nci_release_firmware(ndev);
/* If a patch was applied the new version is checked */
if (patched) {
r = nci_core_init(ndev);
if (r)
goto error;
r = fdp_nci_get_versions(ndev);
if (r)
goto error;
if (info->otp_version != info->otp_patch_version ||
info->ram_version != info->ram_patch_version) {
nfc_err(dev, "Firmware update failed");
r = -EINVAL;
goto error;
}
}
/*
* We initialized the devices but the NFC subsystem expects
* it to not be initialized.
*/
return nci_core_reset(ndev);
error:
fdp_nci_release_firmware(ndev);
nfc_err(dev, "Setup error %d\n", r);
return r;
}
static int fdp_nci_post_setup(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
int r;
/* Check if the device has VSC */
if (info->fw_vsc_cfg && info->fw_vsc_cfg[0]) {
/* Set the vendor specific configuration */
r = fdp_nci_set_production_data(ndev, info->fw_vsc_cfg[3],
&info->fw_vsc_cfg[4]);
if (r) {
nfc_err(dev, "Vendor specific config set error %d\n",
r);
return r;
}
}
/* Set clock type and frequency */
r = fdp_nci_set_clock(ndev, info->clock_type, info->clock_freq);
if (r) {
nfc_err(dev, "Clock set error %d\n", r);
return r;
}
/*
* In order to apply the VSC FDP needs a reset
*/
r = nci_core_reset(ndev);
if (r)
return r;
/**
* The nci core was initialized when post setup was called
* so we leave it like that
*/
return nci_core_init(ndev);
}
static int fdp_nci_core_reset_ntf_packet(struct nci_dev *ndev,
struct sk_buff *skb)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
dev_dbg(dev, "%s\n", __func__);
info->setup_reset_ntf = 1;
wake_up(&info->setup_wq);
return 0;
}
static int fdp_nci_prop_patch_ntf_packet(struct nci_dev *ndev,
struct sk_buff *skb)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
dev_dbg(dev, "%s\n", __func__);
info->setup_patch_ntf = 1;
info->setup_patch_status = skb->data[0];
wake_up(&info->setup_wq);
return 0;
}
static int fdp_nci_prop_patch_rsp_packet(struct nci_dev *ndev,
struct sk_buff *skb)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
u8 status = skb->data[0];
dev_dbg(dev, "%s: status 0x%x\n", __func__, status);
nci_req_complete(ndev, status);
return 0;
}
static int fdp_nci_prop_set_production_data_rsp_packet(struct nci_dev *ndev,
struct sk_buff *skb)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
u8 status = skb->data[0];
dev_dbg(dev, "%s: status 0x%x\n", __func__, status);
nci_req_complete(ndev, status);
return 0;
}
static int fdp_nci_core_get_config_rsp_packet(struct nci_dev *ndev,
struct sk_buff *skb)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
struct nci_core_get_config_rsp *rsp = (void *) skb->data;
u8 i, *p;
if (rsp->status == NCI_STATUS_OK) {
p = rsp->data;
for (i = 0; i < 4; i++) {
switch (*p++) {
case NCI_PARAM_ID_FW_RAM_VERSION:
p++;
info->ram_version = le32_to_cpup((__le32 *) p);
p += 4;
break;
case NCI_PARAM_ID_FW_OTP_VERSION:
p++;
info->otp_version = le32_to_cpup((__le32 *) p);
p += 4;
break;
case NCI_PARAM_ID_OTP_LIMITED_VERSION:
p++;
info->otp_version = le32_to_cpup((__le32 *) p);
p += 4;
break;
case NCI_PARAM_ID_KEY_INDEX_ID:
p++;
info->key_index = *p++;
}
}
}
dev_dbg(dev, "OTP version %d\n", info->otp_version);
dev_dbg(dev, "RAM version %d\n", info->ram_version);
dev_dbg(dev, "key index %d\n", info->key_index);
dev_dbg(dev, "%s: status 0x%x\n", __func__, rsp->status);
nci_req_complete(ndev, rsp->status);
return 0;
}
static struct nci_driver_ops fdp_core_ops[] = {
{
.opcode = NCI_OP_CORE_GET_CONFIG_RSP,
.rsp = fdp_nci_core_get_config_rsp_packet,
},
{
.opcode = NCI_OP_CORE_RESET_NTF,
.ntf = fdp_nci_core_reset_ntf_packet,
},
};
static struct nci_driver_ops fdp_prop_ops[] = {
{
.opcode = nci_opcode_pack(NCI_GID_PROP, NCI_OP_PROP_PATCH_OID),
.rsp = fdp_nci_prop_patch_rsp_packet,
.ntf = fdp_nci_prop_patch_ntf_packet,
},
{
.opcode = nci_opcode_pack(NCI_GID_PROP,
NCI_OP_PROP_SET_PDATA_OID),
.rsp = fdp_nci_prop_set_production_data_rsp_packet,
},
};
struct nci_ops nci_ops = {
.open = fdp_nci_open,
.close = fdp_nci_close,
.send = fdp_nci_send,
.setup = fdp_nci_setup,
.post_setup = fdp_nci_post_setup,
.prop_ops = fdp_prop_ops,
.n_prop_ops = ARRAY_SIZE(fdp_prop_ops),
.core_ops = fdp_core_ops,
.n_core_ops = ARRAY_SIZE(fdp_core_ops),
};
int fdp_nci_probe(struct fdp_i2c_phy *phy, struct nfc_phy_ops *phy_ops,
struct nci_dev **ndevp, int tx_headroom,
int tx_tailroom, u8 clock_type, u32 clock_freq,
u8 *fw_vsc_cfg)
{
struct device *dev = &phy->i2c_dev->dev;
struct fdp_nci_info *info;
struct nci_dev *ndev;
u32 protocols;
int r;
info = kzalloc(sizeof(struct fdp_nci_info), GFP_KERNEL);
if (!info) {
r = -ENOMEM;
goto err_info_alloc;
}
info->phy = phy;
info->phy_ops = phy_ops;
info->clock_type = clock_type;
info->clock_freq = clock_freq;
info->fw_vsc_cfg = fw_vsc_cfg;
init_waitqueue_head(&info->setup_wq);
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 |
NFC_PROTO_ISO15693_MASK;
ndev = nci_allocate_device(&nci_ops, protocols, tx_headroom,
tx_tailroom);
if (!ndev) {
nfc_err(dev, "Cannot allocate nfc ndev\n");
r = -ENOMEM;
goto err_alloc_ndev;
}
r = nci_register_device(ndev);
if (r)
goto err_regdev;
*ndevp = ndev;
info->ndev = ndev;
nci_set_drvdata(ndev, info);
return 0;
err_regdev:
nci_free_device(ndev);
err_alloc_ndev:
kfree(info);
err_info_alloc:
return r;
}
EXPORT_SYMBOL(fdp_nci_probe);
void fdp_nci_remove(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
dev_dbg(dev, "%s\n", __func__);
nci_unregister_device(ndev);
nci_free_device(ndev);
kfree(info);
}
EXPORT_SYMBOL(fdp_nci_remove);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("NFC NCI driver for Intel Fields Peak NFC controller");
MODULE_AUTHOR("Robert Dolca <robert.dolca@intel.com>");