summaryrefslogtreecommitdiffstats
path: root/drivers/nfc/st-nci
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:02:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:02:30 +0000
commit76cb841cb886eef6b3bee341a2266c76578724ad (patch)
treef5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/nfc/st-nci
parentInitial commit. (diff)
downloadlinux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz
linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/nfc/st-nci')
-rw-r--r--drivers/nfc/st-nci/Kconfig30
-rw-r--r--drivers/nfc/st-nci/Makefile13
-rw-r--r--drivers/nfc/st-nci/core.c187
-rw-r--r--drivers/nfc/st-nci/i2c.c316
-rw-r--r--drivers/nfc/st-nci/ndlc.c311
-rw-r--r--drivers/nfc/st-nci/ndlc.h63
-rw-r--r--drivers/nfc/st-nci/se.c762
-rw-r--r--drivers/nfc/st-nci/spi.c329
-rw-r--r--drivers/nfc/st-nci/st-nci.h153
-rw-r--r--drivers/nfc/st-nci/vendor_cmds.c478
10 files changed, 2642 insertions, 0 deletions
diff --git a/drivers/nfc/st-nci/Kconfig b/drivers/nfc/st-nci/Kconfig
new file mode 100644
index 000000000..5c6e21ccb
--- /dev/null
+++ b/drivers/nfc/st-nci/Kconfig
@@ -0,0 +1,30 @@
+config NFC_ST_NCI
+ tristate
+ ---help---
+ STMicroelectronics NFC NCI chips core driver. It implements the chipset
+ NCI logic and hooks into the NFC kernel APIs. Physical layers will
+ register against it.
+
+config NFC_ST_NCI_I2C
+ tristate "STMicroelectronics ST NCI NFC driver (I2C)"
+ depends on NFC_NCI && I2C
+ select NFC_ST_NCI
+ ---help---
+ This module adds support for an I2C interface to the
+ STMicroelectronics NFC NCI chips family.
+ Select this if your platform is using the i2c bus.
+
+ If you choose to build a module, it'll be called st-nci_i2c.
+ Say N if unsure.
+
+config NFC_ST_NCI_SPI
+ tristate "STMicroelectronics ST NCI NFC driver (SPI)"
+ depends on NFC_NCI && SPI
+ select NFC_ST_NCI
+ ---help---
+ This module adds support for an SPI interface to the
+ STMicroelectronics NFC NCI chips family.
+ Select this if your platform is using the spi bus.
+
+ If you choose to build a module, it'll be called st-nci_spi.
+ Say N if unsure.
diff --git a/drivers/nfc/st-nci/Makefile b/drivers/nfc/st-nci/Makefile
new file mode 100644
index 000000000..e0310743f
--- /dev/null
+++ b/drivers/nfc/st-nci/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for ST_NCI NCI based NFC driver
+#
+
+st-nci-objs = ndlc.o core.o se.o vendor_cmds.o
+obj-$(CONFIG_NFC_ST_NCI) += st-nci.o
+
+st-nci_i2c-objs = i2c.o
+obj-$(CONFIG_NFC_ST_NCI_I2C) += st-nci_i2c.o
+
+st-nci_spi-objs = spi.o
+obj-$(CONFIG_NFC_ST_NCI_SPI) += st-nci_spi.o
diff --git a/drivers/nfc/st-nci/core.c b/drivers/nfc/st-nci/core.c
new file mode 100644
index 000000000..c693128ee
--- /dev/null
+++ b/drivers/nfc/st-nci/core.c
@@ -0,0 +1,187 @@
+/*
+ * NCI based Driver for STMicroelectronics NFC Chip
+ *
+ * Copyright (C) 2014-2015 STMicroelectronics SAS. 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/module.h>
+#include <linux/nfc.h>
+#include <net/nfc/nci.h>
+#include <net/nfc/nci_core.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+
+#include "st-nci.h"
+
+#define DRIVER_DESC "NCI NFC driver for ST_NCI"
+
+#define ST_NCI1_X_PROPRIETARY_ISO15693 0x83
+
+static int st_nci_init(struct nci_dev *ndev)
+{
+ struct nci_mode_set_cmd cmd;
+
+ cmd.cmd_type = ST_NCI_SET_NFC_MODE;
+ cmd.mode = 1;
+
+ return nci_prop_cmd(ndev, ST_NCI_CORE_PROP,
+ sizeof(struct nci_mode_set_cmd), (__u8 *)&cmd);
+}
+
+static int st_nci_open(struct nci_dev *ndev)
+{
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+ int r;
+
+ if (test_and_set_bit(ST_NCI_RUNNING, &info->flags))
+ return 0;
+
+ r = ndlc_open(info->ndlc);
+ if (r)
+ clear_bit(ST_NCI_RUNNING, &info->flags);
+
+ return r;
+}
+
+static int st_nci_close(struct nci_dev *ndev)
+{
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+
+ if (!test_bit(ST_NCI_RUNNING, &info->flags))
+ return 0;
+
+ ndlc_close(info->ndlc);
+
+ clear_bit(ST_NCI_RUNNING, &info->flags);
+
+ return 0;
+}
+
+static int st_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
+{
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+
+ skb->dev = (void *)ndev;
+
+ if (!test_bit(ST_NCI_RUNNING, &info->flags))
+ return -EBUSY;
+
+ return ndlc_send(info->ndlc, skb);
+}
+
+static __u32 st_nci_get_rfprotocol(struct nci_dev *ndev,
+ __u8 rf_protocol)
+{
+ return rf_protocol == ST_NCI1_X_PROPRIETARY_ISO15693 ?
+ NFC_PROTO_ISO15693_MASK : 0;
+}
+
+static int st_nci_prop_rsp_packet(struct nci_dev *ndev,
+ struct sk_buff *skb)
+{
+ __u8 status = skb->data[0];
+
+ nci_req_complete(ndev, status);
+ return 0;
+}
+
+static struct nci_driver_ops st_nci_prop_ops[] = {
+ {
+ .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
+ ST_NCI_CORE_PROP),
+ .rsp = st_nci_prop_rsp_packet,
+ },
+};
+
+static struct nci_ops st_nci_ops = {
+ .init = st_nci_init,
+ .open = st_nci_open,
+ .close = st_nci_close,
+ .send = st_nci_send,
+ .get_rfprotocol = st_nci_get_rfprotocol,
+ .discover_se = st_nci_discover_se,
+ .enable_se = st_nci_enable_se,
+ .disable_se = st_nci_disable_se,
+ .se_io = st_nci_se_io,
+ .hci_load_session = st_nci_hci_load_session,
+ .hci_event_received = st_nci_hci_event_received,
+ .hci_cmd_received = st_nci_hci_cmd_received,
+ .prop_ops = st_nci_prop_ops,
+ .n_prop_ops = ARRAY_SIZE(st_nci_prop_ops),
+};
+
+int st_nci_probe(struct llt_ndlc *ndlc, int phy_headroom,
+ int phy_tailroom, struct st_nci_se_status *se_status)
+{
+ struct st_nci_info *info;
+ int r;
+ u32 protocols;
+
+ info = devm_kzalloc(ndlc->dev,
+ sizeof(struct st_nci_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ 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;
+
+ ndlc->ndev = nci_allocate_device(&st_nci_ops, protocols,
+ phy_headroom, phy_tailroom);
+ if (!ndlc->ndev) {
+ pr_err("Cannot allocate nfc ndev\n");
+ return -ENOMEM;
+ }
+ info->ndlc = ndlc;
+
+ nci_set_drvdata(ndlc->ndev, info);
+
+ r = st_nci_vendor_cmds_init(ndlc->ndev);
+ if (r) {
+ pr_err("Cannot register proprietary vendor cmds\n");
+ goto err_reg_dev;
+ }
+
+ r = nci_register_device(ndlc->ndev);
+ if (r) {
+ pr_err("Cannot register nfc device to nci core\n");
+ goto err_reg_dev;
+ }
+
+ return st_nci_se_init(ndlc->ndev, se_status);
+
+err_reg_dev:
+ nci_free_device(ndlc->ndev);
+ return r;
+}
+EXPORT_SYMBOL_GPL(st_nci_probe);
+
+void st_nci_remove(struct nci_dev *ndev)
+{
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+
+ ndlc_close(info->ndlc);
+
+ nci_unregister_device(ndev);
+ nci_free_device(ndev);
+}
+EXPORT_SYMBOL_GPL(st_nci_remove);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION(DRIVER_DESC);
diff --git a/drivers/nfc/st-nci/i2c.c b/drivers/nfc/st-nci/i2c.c
new file mode 100644
index 000000000..f9525ef87
--- /dev/null
+++ b/drivers/nfc/st-nci/i2c.c
@@ -0,0 +1,316 @@
+/*
+ * I2C Link Layer for ST NCI NFC controller familly based Driver
+ * Copyright (C) 2014-2015 STMicroelectronics SAS. 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/>.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/gpio/consumer.h>
+#include <linux/acpi.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/nfc.h>
+#include <linux/of.h>
+
+#include "st-nci.h"
+
+#define DRIVER_DESC "NCI NFC driver for ST_NCI"
+
+/* ndlc header */
+#define ST_NCI_FRAME_HEADROOM 1
+#define ST_NCI_FRAME_TAILROOM 0
+
+#define ST_NCI_I2C_MIN_SIZE 4 /* PCB(1) + NCI Packet header(3) */
+#define ST_NCI_I2C_MAX_SIZE 250 /* req 4.2.1 */
+
+#define ST_NCI_DRIVER_NAME "st_nci"
+#define ST_NCI_I2C_DRIVER_NAME "st_nci_i2c"
+
+struct st_nci_i2c_phy {
+ struct i2c_client *i2c_dev;
+ struct llt_ndlc *ndlc;
+
+ bool irq_active;
+
+ struct gpio_desc *gpiod_reset;
+
+ struct st_nci_se_status se_status;
+};
+
+static int st_nci_i2c_enable(void *phy_id)
+{
+ struct st_nci_i2c_phy *phy = phy_id;
+
+ gpiod_set_value(phy->gpiod_reset, 0);
+ usleep_range(10000, 15000);
+ gpiod_set_value(phy->gpiod_reset, 1);
+ usleep_range(80000, 85000);
+
+ if (phy->ndlc->powered == 0 && phy->irq_active == 0) {
+ enable_irq(phy->i2c_dev->irq);
+ phy->irq_active = true;
+ }
+
+ return 0;
+}
+
+static void st_nci_i2c_disable(void *phy_id)
+{
+ struct st_nci_i2c_phy *phy = phy_id;
+
+ disable_irq_nosync(phy->i2c_dev->irq);
+ phy->irq_active = false;
+}
+
+/*
+ * Writing a frame must not return the number of written bytes.
+ * It must return either zero for success, or <0 for error.
+ * In addition, it must not alter the skb
+ */
+static int st_nci_i2c_write(void *phy_id, struct sk_buff *skb)
+{
+ int r = -1;
+ struct st_nci_i2c_phy *phy = phy_id;
+ struct i2c_client *client = phy->i2c_dev;
+
+ if (phy->ndlc->hard_fault != 0)
+ return phy->ndlc->hard_fault;
+
+ r = i2c_master_send(client, skb->data, skb->len);
+ if (r < 0) { /* Retry, chip was in standby */
+ usleep_range(1000, 4000);
+ r = i2c_master_send(client, skb->data, skb->len);
+ }
+
+ if (r >= 0) {
+ if (r != skb->len)
+ r = -EREMOTEIO;
+ else
+ r = 0;
+ }
+
+ return r;
+}
+
+/*
+ * Reads an ndlc frame and returns it in a newly allocated sk_buff.
+ * returns:
+ * 0 : if received frame is complete
+ * -EREMOTEIO : i2c read error (fatal)
+ * -EBADMSG : frame was incorrect and discarded
+ * -ENOMEM : cannot allocate skb, frame dropped
+ */
+static int st_nci_i2c_read(struct st_nci_i2c_phy *phy,
+ struct sk_buff **skb)
+{
+ int r;
+ u8 len;
+ u8 buf[ST_NCI_I2C_MAX_SIZE];
+ struct i2c_client *client = phy->i2c_dev;
+
+ r = i2c_master_recv(client, buf, ST_NCI_I2C_MIN_SIZE);
+ if (r < 0) { /* Retry, chip was in standby */
+ usleep_range(1000, 4000);
+ r = i2c_master_recv(client, buf, ST_NCI_I2C_MIN_SIZE);
+ }
+
+ if (r != ST_NCI_I2C_MIN_SIZE)
+ return -EREMOTEIO;
+
+ len = be16_to_cpu(*(__be16 *) (buf + 2));
+ if (len > ST_NCI_I2C_MAX_SIZE) {
+ nfc_err(&client->dev, "invalid frame len\n");
+ return -EBADMSG;
+ }
+
+ *skb = alloc_skb(ST_NCI_I2C_MIN_SIZE + len, GFP_KERNEL);
+ if (*skb == NULL)
+ return -ENOMEM;
+
+ skb_reserve(*skb, ST_NCI_I2C_MIN_SIZE);
+ skb_put(*skb, ST_NCI_I2C_MIN_SIZE);
+ memcpy((*skb)->data, buf, ST_NCI_I2C_MIN_SIZE);
+
+ if (!len)
+ return 0;
+
+ r = i2c_master_recv(client, buf, len);
+ if (r != len) {
+ kfree_skb(*skb);
+ return -EREMOTEIO;
+ }
+
+ skb_put(*skb, len);
+ memcpy((*skb)->data + ST_NCI_I2C_MIN_SIZE, buf, len);
+
+ return 0;
+}
+
+/*
+ * Reads an ndlc frame from the chip.
+ *
+ * On ST_NCI, IRQ goes in idle state when read starts.
+ */
+static irqreturn_t st_nci_irq_thread_fn(int irq, void *phy_id)
+{
+ struct st_nci_i2c_phy *phy = phy_id;
+ struct i2c_client *client;
+ struct sk_buff *skb = NULL;
+ int r;
+
+ if (!phy || !phy->ndlc || irq != phy->i2c_dev->irq) {
+ WARN_ON_ONCE(1);
+ return IRQ_NONE;
+ }
+
+ client = phy->i2c_dev;
+ dev_dbg(&client->dev, "IRQ\n");
+
+ if (phy->ndlc->hard_fault)
+ return IRQ_HANDLED;
+
+ if (!phy->ndlc->powered) {
+ st_nci_i2c_disable(phy);
+ return IRQ_HANDLED;
+ }
+
+ r = st_nci_i2c_read(phy, &skb);
+ if (r == -EREMOTEIO || r == -ENOMEM || r == -EBADMSG)
+ return IRQ_HANDLED;
+
+ ndlc_recv(phy->ndlc, skb);
+
+ return IRQ_HANDLED;
+}
+
+static struct nfc_phy_ops i2c_phy_ops = {
+ .write = st_nci_i2c_write,
+ .enable = st_nci_i2c_enable,
+ .disable = st_nci_i2c_disable,
+};
+
+static const struct acpi_gpio_params reset_gpios = { 1, 0, false };
+
+static const struct acpi_gpio_mapping acpi_st_nci_gpios[] = {
+ { "reset-gpios", &reset_gpios, 1 },
+ {},
+};
+
+static int st_nci_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct st_nci_i2c_phy *phy;
+ int r;
+
+ dev_dbg(&client->dev, "%s\n", __func__);
+ dev_dbg(&client->dev, "IRQ: %d\n", client->irq);
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ nfc_err(&client->dev, "Need I2C_FUNC_I2C\n");
+ return -ENODEV;
+ }
+
+ phy = devm_kzalloc(dev, sizeof(struct st_nci_i2c_phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ phy->i2c_dev = client;
+
+ i2c_set_clientdata(client, phy);
+
+ r = devm_acpi_dev_add_driver_gpios(dev, acpi_st_nci_gpios);
+ if (r)
+ dev_dbg(dev, "Unable to add GPIO mapping table\n");
+
+ /* Get RESET GPIO */
+ phy->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(phy->gpiod_reset)) {
+ nfc_err(dev, "Unable to get RESET GPIO\n");
+ return -ENODEV;
+ }
+
+ phy->se_status.is_ese_present =
+ device_property_read_bool(dev, "ese-present");
+ phy->se_status.is_uicc_present =
+ device_property_read_bool(dev, "uicc-present");
+
+ r = ndlc_probe(phy, &i2c_phy_ops, &client->dev,
+ ST_NCI_FRAME_HEADROOM, ST_NCI_FRAME_TAILROOM,
+ &phy->ndlc, &phy->se_status);
+ if (r < 0) {
+ nfc_err(&client->dev, "Unable to register ndlc layer\n");
+ return r;
+ }
+
+ phy->irq_active = true;
+ r = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ st_nci_irq_thread_fn,
+ IRQF_ONESHOT,
+ ST_NCI_DRIVER_NAME, phy);
+ if (r < 0)
+ nfc_err(&client->dev, "Unable to register IRQ handler\n");
+
+ return r;
+}
+
+static int st_nci_i2c_remove(struct i2c_client *client)
+{
+ struct st_nci_i2c_phy *phy = i2c_get_clientdata(client);
+
+ dev_dbg(&client->dev, "%s\n", __func__);
+
+ ndlc_remove(phy->ndlc);
+
+ return 0;
+}
+
+static const struct i2c_device_id st_nci_i2c_id_table[] = {
+ {ST_NCI_DRIVER_NAME, 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, st_nci_i2c_id_table);
+
+static const struct acpi_device_id st_nci_i2c_acpi_match[] = {
+ {"SMO2101"},
+ {"SMO2102"},
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, st_nci_i2c_acpi_match);
+
+static const struct of_device_id of_st_nci_i2c_match[] = {
+ { .compatible = "st,st21nfcb-i2c", },
+ { .compatible = "st,st21nfcb_i2c", },
+ { .compatible = "st,st21nfcc-i2c", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, of_st_nci_i2c_match);
+
+static struct i2c_driver st_nci_i2c_driver = {
+ .driver = {
+ .name = ST_NCI_I2C_DRIVER_NAME,
+ .of_match_table = of_match_ptr(of_st_nci_i2c_match),
+ .acpi_match_table = ACPI_PTR(st_nci_i2c_acpi_match),
+ },
+ .probe = st_nci_i2c_probe,
+ .id_table = st_nci_i2c_id_table,
+ .remove = st_nci_i2c_remove,
+};
+module_i2c_driver(st_nci_i2c_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION(DRIVER_DESC);
diff --git a/drivers/nfc/st-nci/ndlc.c b/drivers/nfc/st-nci/ndlc.c
new file mode 100644
index 000000000..f26d938d2
--- /dev/null
+++ b/drivers/nfc/st-nci/ndlc.c
@@ -0,0 +1,311 @@
+/*
+ * Low Level Transport (NDLC) Driver for STMicroelectronics NFC Chip
+ *
+ * Copyright (C) 2014-2015 STMicroelectronics SAS. 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/sched.h>
+#include <net/nfc/nci_core.h>
+
+#include "st-nci.h"
+
+#define NDLC_TIMER_T1 100
+#define NDLC_TIMER_T1_WAIT 400
+#define NDLC_TIMER_T2 1200
+
+#define PCB_TYPE_DATAFRAME 0x80
+#define PCB_TYPE_SUPERVISOR 0xc0
+#define PCB_TYPE_MASK PCB_TYPE_SUPERVISOR
+
+#define PCB_SYNC_ACK 0x20
+#define PCB_SYNC_NACK 0x10
+#define PCB_SYNC_WAIT 0x30
+#define PCB_SYNC_NOINFO 0x00
+#define PCB_SYNC_MASK PCB_SYNC_WAIT
+
+#define PCB_DATAFRAME_RETRANSMIT_YES 0x00
+#define PCB_DATAFRAME_RETRANSMIT_NO 0x04
+#define PCB_DATAFRAME_RETRANSMIT_MASK PCB_DATAFRAME_RETRANSMIT_NO
+
+#define PCB_SUPERVISOR_RETRANSMIT_YES 0x00
+#define PCB_SUPERVISOR_RETRANSMIT_NO 0x02
+#define PCB_SUPERVISOR_RETRANSMIT_MASK PCB_SUPERVISOR_RETRANSMIT_NO
+
+#define PCB_FRAME_CRC_INFO_PRESENT 0x08
+#define PCB_FRAME_CRC_INFO_NOTPRESENT 0x00
+#define PCB_FRAME_CRC_INFO_MASK PCB_FRAME_CRC_INFO_PRESENT
+
+#define NDLC_DUMP_SKB(info, skb) \
+do { \
+ pr_debug("%s:\n", info); \
+ print_hex_dump(KERN_DEBUG, "ndlc: ", DUMP_PREFIX_OFFSET, \
+ 16, 1, skb->data, skb->len, 0); \
+} while (0)
+
+int ndlc_open(struct llt_ndlc *ndlc)
+{
+ /* toggle reset pin */
+ ndlc->ops->enable(ndlc->phy_id);
+ ndlc->powered = 1;
+ return 0;
+}
+EXPORT_SYMBOL(ndlc_open);
+
+void ndlc_close(struct llt_ndlc *ndlc)
+{
+ struct nci_mode_set_cmd cmd;
+
+ cmd.cmd_type = ST_NCI_SET_NFC_MODE;
+ cmd.mode = 0;
+
+ /* toggle reset pin */
+ ndlc->ops->enable(ndlc->phy_id);
+
+ nci_prop_cmd(ndlc->ndev, ST_NCI_CORE_PROP,
+ sizeof(struct nci_mode_set_cmd), (__u8 *)&cmd);
+
+ ndlc->powered = 0;
+ ndlc->ops->disable(ndlc->phy_id);
+}
+EXPORT_SYMBOL(ndlc_close);
+
+int ndlc_send(struct llt_ndlc *ndlc, struct sk_buff *skb)
+{
+ /* add ndlc header */
+ u8 pcb = PCB_TYPE_DATAFRAME | PCB_DATAFRAME_RETRANSMIT_NO |
+ PCB_FRAME_CRC_INFO_NOTPRESENT;
+
+ *(u8 *)skb_push(skb, 1) = pcb;
+ skb_queue_tail(&ndlc->send_q, skb);
+
+ schedule_work(&ndlc->sm_work);
+
+ return 0;
+}
+EXPORT_SYMBOL(ndlc_send);
+
+static void llt_ndlc_send_queue(struct llt_ndlc *ndlc)
+{
+ struct sk_buff *skb;
+ int r;
+ unsigned long time_sent;
+
+ if (ndlc->send_q.qlen)
+ pr_debug("sendQlen=%d unackQlen=%d\n",
+ ndlc->send_q.qlen, ndlc->ack_pending_q.qlen);
+
+ while (ndlc->send_q.qlen) {
+ skb = skb_dequeue(&ndlc->send_q);
+ NDLC_DUMP_SKB("ndlc frame written", skb);
+ r = ndlc->ops->write(ndlc->phy_id, skb);
+ if (r < 0) {
+ ndlc->hard_fault = r;
+ break;
+ }
+ time_sent = jiffies;
+ *(unsigned long *)skb->cb = time_sent;
+
+ skb_queue_tail(&ndlc->ack_pending_q, skb);
+
+ /* start timer t1 for ndlc aknowledge */
+ ndlc->t1_active = true;
+ mod_timer(&ndlc->t1_timer, time_sent +
+ msecs_to_jiffies(NDLC_TIMER_T1));
+ /* start timer t2 for chip availability */
+ ndlc->t2_active = true;
+ mod_timer(&ndlc->t2_timer, time_sent +
+ msecs_to_jiffies(NDLC_TIMER_T2));
+ }
+}
+
+static void llt_ndlc_requeue_data_pending(struct llt_ndlc *ndlc)
+{
+ struct sk_buff *skb;
+ u8 pcb;
+
+ while ((skb = skb_dequeue_tail(&ndlc->ack_pending_q))) {
+ pcb = skb->data[0];
+ switch (pcb & PCB_TYPE_MASK) {
+ case PCB_TYPE_SUPERVISOR:
+ skb->data[0] = (pcb & ~PCB_SUPERVISOR_RETRANSMIT_MASK) |
+ PCB_SUPERVISOR_RETRANSMIT_YES;
+ break;
+ case PCB_TYPE_DATAFRAME:
+ skb->data[0] = (pcb & ~PCB_DATAFRAME_RETRANSMIT_MASK) |
+ PCB_DATAFRAME_RETRANSMIT_YES;
+ break;
+ default:
+ pr_err("UNKNOWN Packet Control Byte=%d\n", pcb);
+ kfree_skb(skb);
+ continue;
+ }
+ skb_queue_head(&ndlc->send_q, skb);
+ }
+}
+
+static void llt_ndlc_rcv_queue(struct llt_ndlc *ndlc)
+{
+ struct sk_buff *skb;
+ u8 pcb;
+ unsigned long time_sent;
+
+ if (ndlc->rcv_q.qlen)
+ pr_debug("rcvQlen=%d\n", ndlc->rcv_q.qlen);
+
+ while ((skb = skb_dequeue(&ndlc->rcv_q)) != NULL) {
+ pcb = skb->data[0];
+ skb_pull(skb, 1);
+ if ((pcb & PCB_TYPE_MASK) == PCB_TYPE_SUPERVISOR) {
+ switch (pcb & PCB_SYNC_MASK) {
+ case PCB_SYNC_ACK:
+ skb = skb_dequeue(&ndlc->ack_pending_q);
+ kfree_skb(skb);
+ del_timer_sync(&ndlc->t1_timer);
+ del_timer_sync(&ndlc->t2_timer);
+ ndlc->t2_active = false;
+ ndlc->t1_active = false;
+ break;
+ case PCB_SYNC_NACK:
+ llt_ndlc_requeue_data_pending(ndlc);
+ llt_ndlc_send_queue(ndlc);
+ /* start timer t1 for ndlc aknowledge */
+ time_sent = jiffies;
+ ndlc->t1_active = true;
+ mod_timer(&ndlc->t1_timer, time_sent +
+ msecs_to_jiffies(NDLC_TIMER_T1));
+ break;
+ case PCB_SYNC_WAIT:
+ time_sent = jiffies;
+ ndlc->t1_active = true;
+ mod_timer(&ndlc->t1_timer, time_sent +
+ msecs_to_jiffies(NDLC_TIMER_T1_WAIT));
+ break;
+ default:
+ kfree_skb(skb);
+ break;
+ }
+ } else if ((pcb & PCB_TYPE_MASK) == PCB_TYPE_DATAFRAME) {
+ nci_recv_frame(ndlc->ndev, skb);
+ } else {
+ kfree_skb(skb);
+ }
+ }
+}
+
+static void llt_ndlc_sm_work(struct work_struct *work)
+{
+ struct llt_ndlc *ndlc = container_of(work, struct llt_ndlc, sm_work);
+
+ llt_ndlc_send_queue(ndlc);
+ llt_ndlc_rcv_queue(ndlc);
+
+ if (ndlc->t1_active && timer_pending(&ndlc->t1_timer) == 0) {
+ pr_debug
+ ("Handle T1(recv SUPERVISOR) elapsed (T1 now inactive)\n");
+ ndlc->t1_active = false;
+
+ llt_ndlc_requeue_data_pending(ndlc);
+ llt_ndlc_send_queue(ndlc);
+ }
+
+ if (ndlc->t2_active && timer_pending(&ndlc->t2_timer) == 0) {
+ pr_debug("Handle T2(recv DATA) elapsed (T2 now inactive)\n");
+ ndlc->t2_active = false;
+ ndlc->t1_active = false;
+ del_timer_sync(&ndlc->t1_timer);
+ del_timer_sync(&ndlc->t2_timer);
+ ndlc_close(ndlc);
+ ndlc->hard_fault = -EREMOTEIO;
+ }
+}
+
+void ndlc_recv(struct llt_ndlc *ndlc, struct sk_buff *skb)
+{
+ if (skb == NULL) {
+ pr_err("NULL Frame -> link is dead\n");
+ ndlc->hard_fault = -EREMOTEIO;
+ ndlc_close(ndlc);
+ } else {
+ NDLC_DUMP_SKB("incoming frame", skb);
+ skb_queue_tail(&ndlc->rcv_q, skb);
+ }
+
+ schedule_work(&ndlc->sm_work);
+}
+EXPORT_SYMBOL(ndlc_recv);
+
+static void ndlc_t1_timeout(struct timer_list *t)
+{
+ struct llt_ndlc *ndlc = from_timer(ndlc, t, t1_timer);
+
+ pr_debug("\n");
+
+ schedule_work(&ndlc->sm_work);
+}
+
+static void ndlc_t2_timeout(struct timer_list *t)
+{
+ struct llt_ndlc *ndlc = from_timer(ndlc, t, t2_timer);
+
+ pr_debug("\n");
+
+ schedule_work(&ndlc->sm_work);
+}
+
+int ndlc_probe(void *phy_id, struct nfc_phy_ops *phy_ops, struct device *dev,
+ int phy_headroom, int phy_tailroom, struct llt_ndlc **ndlc_id,
+ struct st_nci_se_status *se_status)
+{
+ struct llt_ndlc *ndlc;
+
+ ndlc = devm_kzalloc(dev, sizeof(struct llt_ndlc), GFP_KERNEL);
+ if (!ndlc)
+ return -ENOMEM;
+
+ ndlc->ops = phy_ops;
+ ndlc->phy_id = phy_id;
+ ndlc->dev = dev;
+ ndlc->powered = 0;
+
+ *ndlc_id = ndlc;
+
+ /* initialize timers */
+ timer_setup(&ndlc->t1_timer, ndlc_t1_timeout, 0);
+ timer_setup(&ndlc->t2_timer, ndlc_t2_timeout, 0);
+
+ skb_queue_head_init(&ndlc->rcv_q);
+ skb_queue_head_init(&ndlc->send_q);
+ skb_queue_head_init(&ndlc->ack_pending_q);
+
+ INIT_WORK(&ndlc->sm_work, llt_ndlc_sm_work);
+
+ return st_nci_probe(ndlc, phy_headroom, phy_tailroom, se_status);
+}
+EXPORT_SYMBOL(ndlc_probe);
+
+void ndlc_remove(struct llt_ndlc *ndlc)
+{
+ st_nci_remove(ndlc->ndev);
+
+ /* cancel timers */
+ del_timer_sync(&ndlc->t1_timer);
+ del_timer_sync(&ndlc->t2_timer);
+ ndlc->t2_active = false;
+ ndlc->t1_active = false;
+
+ skb_queue_purge(&ndlc->rcv_q);
+ skb_queue_purge(&ndlc->send_q);
+}
+EXPORT_SYMBOL(ndlc_remove);
diff --git a/drivers/nfc/st-nci/ndlc.h b/drivers/nfc/st-nci/ndlc.h
new file mode 100644
index 000000000..bdf78ffd5
--- /dev/null
+++ b/drivers/nfc/st-nci/ndlc.h
@@ -0,0 +1,63 @@
+/*
+ * NCI based Driver for STMicroelectronics NFC Chip
+ *
+ * Copyright (C) 2014-2015 STMicroelectronics SAS. 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_NDLC_H_
+#define __LOCAL_NDLC_H_
+
+#include <linux/skbuff.h>
+#include <net/nfc/nfc.h>
+
+struct st_nci_se_status;
+
+/* Low Level Transport description */
+struct llt_ndlc {
+ struct nci_dev *ndev;
+ struct nfc_phy_ops *ops;
+ void *phy_id;
+
+ struct timer_list t1_timer;
+ bool t1_active;
+
+ struct timer_list t2_timer;
+ bool t2_active;
+
+ struct sk_buff_head rcv_q;
+ struct sk_buff_head send_q;
+ struct sk_buff_head ack_pending_q;
+
+ struct work_struct sm_work;
+
+ struct device *dev;
+
+ /*
+ * < 0 if hardware error occurred
+ * and prevents normal operation.
+ */
+ int hard_fault;
+ int powered;
+};
+
+int ndlc_open(struct llt_ndlc *ndlc);
+void ndlc_close(struct llt_ndlc *ndlc);
+int ndlc_send(struct llt_ndlc *ndlc, struct sk_buff *skb);
+void ndlc_recv(struct llt_ndlc *ndlc, struct sk_buff *skb);
+int ndlc_probe(void *phy_id, struct nfc_phy_ops *phy_ops, struct device *dev,
+ int phy_headroom, int phy_tailroom, struct llt_ndlc **ndlc_id,
+ struct st_nci_se_status *se_status);
+void ndlc_remove(struct llt_ndlc *ndlc);
+#endif /* __LOCAL_NDLC_H__ */
diff --git a/drivers/nfc/st-nci/se.c b/drivers/nfc/st-nci/se.c
new file mode 100644
index 000000000..5d6e7e931
--- /dev/null
+++ b/drivers/nfc/st-nci/se.c
@@ -0,0 +1,762 @@
+/*
+ * Secure Element driver for STMicroelectronics NFC NCI chip
+ *
+ * Copyright (C) 2014-2015 STMicroelectronics SAS. 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/module.h>
+#include <linux/nfc.h>
+#include <linux/delay.h>
+#include <net/nfc/nci.h>
+#include <net/nfc/nci_core.h>
+
+#include "st-nci.h"
+
+struct st_nci_pipe_info {
+ u8 pipe_state;
+ u8 src_host_id;
+ u8 src_gate_id;
+ u8 dst_host_id;
+ u8 dst_gate_id;
+} __packed;
+
+/* Hosts */
+#define ST_NCI_HOST_CONTROLLER_ID 0x00
+#define ST_NCI_TERMINAL_HOST_ID 0x01
+#define ST_NCI_UICC_HOST_ID 0x02
+#define ST_NCI_ESE_HOST_ID 0xc0
+
+/* Gates */
+#define ST_NCI_APDU_READER_GATE 0xf0
+#define ST_NCI_CONNECTIVITY_GATE 0x41
+
+/* Pipes */
+#define ST_NCI_DEVICE_MGNT_PIPE 0x02
+
+/* Connectivity pipe only */
+#define ST_NCI_SE_COUNT_PIPE_UICC 0x01
+/* Connectivity + APDU Reader pipe */
+#define ST_NCI_SE_COUNT_PIPE_EMBEDDED 0x02
+
+#define ST_NCI_SE_TO_HOT_PLUG 1000 /* msecs */
+#define ST_NCI_SE_TO_PIPES 2000
+
+#define ST_NCI_EVT_HOT_PLUG_IS_INHIBITED(x) (x->data[0] & 0x80)
+
+#define NCI_HCI_APDU_PARAM_ATR 0x01
+#define NCI_HCI_ADMIN_PARAM_SESSION_IDENTITY 0x01
+#define NCI_HCI_ADMIN_PARAM_WHITELIST 0x03
+#define NCI_HCI_ADMIN_PARAM_HOST_LIST 0x04
+
+#define ST_NCI_EVT_SE_HARD_RESET 0x20
+#define ST_NCI_EVT_TRANSMIT_DATA 0x10
+#define ST_NCI_EVT_WTX_REQUEST 0x11
+#define ST_NCI_EVT_SE_SOFT_RESET 0x11
+#define ST_NCI_EVT_SE_END_OF_APDU_TRANSFER 0x21
+#define ST_NCI_EVT_HOT_PLUG 0x03
+
+#define ST_NCI_SE_MODE_OFF 0x00
+#define ST_NCI_SE_MODE_ON 0x01
+
+#define ST_NCI_EVT_CONNECTIVITY 0x10
+#define ST_NCI_EVT_TRANSACTION 0x12
+
+#define ST_NCI_DM_GETINFO 0x13
+#define ST_NCI_DM_GETINFO_PIPE_LIST 0x02
+#define ST_NCI_DM_GETINFO_PIPE_INFO 0x01
+#define ST_NCI_DM_PIPE_CREATED 0x02
+#define ST_NCI_DM_PIPE_OPEN 0x04
+#define ST_NCI_DM_RF_ACTIVE 0x80
+#define ST_NCI_DM_DISCONNECT 0x30
+
+#define ST_NCI_DM_IS_PIPE_OPEN(p) \
+ ((p & 0x0f) == (ST_NCI_DM_PIPE_CREATED | ST_NCI_DM_PIPE_OPEN))
+
+#define ST_NCI_ATR_DEFAULT_BWI 0x04
+
+/*
+ * WT = 2^BWI/10[s], convert into msecs and add a secure
+ * room by increasing by 2 this timeout
+ */
+#define ST_NCI_BWI_TO_TIMEOUT(x) ((1 << x) * 200)
+#define ST_NCI_ATR_GET_Y_FROM_TD(x) (x >> 4)
+
+/* If TA is present bit 0 is set */
+#define ST_NCI_ATR_TA_PRESENT(x) (x & 0x01)
+/* If TB is present bit 1 is set */
+#define ST_NCI_ATR_TB_PRESENT(x) (x & 0x02)
+
+#define ST_NCI_NUM_DEVICES 256
+
+static DECLARE_BITMAP(dev_mask, ST_NCI_NUM_DEVICES);
+
+/* Here are the mandatory pipe for st_nci */
+static struct nci_hci_gate st_nci_gates[] = {
+ {NCI_HCI_ADMIN_GATE, NCI_HCI_ADMIN_PIPE,
+ ST_NCI_HOST_CONTROLLER_ID},
+ {NCI_HCI_LINK_MGMT_GATE, NCI_HCI_LINK_MGMT_PIPE,
+ ST_NCI_HOST_CONTROLLER_ID},
+ {ST_NCI_DEVICE_MGNT_GATE, ST_NCI_DEVICE_MGNT_PIPE,
+ ST_NCI_HOST_CONTROLLER_ID},
+
+ {NCI_HCI_IDENTITY_MGMT_GATE, NCI_HCI_INVALID_PIPE,
+ ST_NCI_HOST_CONTROLLER_ID},
+
+ /* Secure element pipes are created by secure element host */
+ {ST_NCI_CONNECTIVITY_GATE, NCI_HCI_DO_NOT_OPEN_PIPE,
+ ST_NCI_HOST_CONTROLLER_ID},
+ {ST_NCI_APDU_READER_GATE, NCI_HCI_DO_NOT_OPEN_PIPE,
+ ST_NCI_HOST_CONTROLLER_ID},
+};
+
+static u8 st_nci_se_get_bwi(struct nci_dev *ndev)
+{
+ int i;
+ u8 td;
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+
+ /* Bits 8 to 5 of the first TB for T=1 encode BWI from zero to nine */
+ for (i = 1; i < ST_NCI_ESE_MAX_LENGTH; i++) {
+ td = ST_NCI_ATR_GET_Y_FROM_TD(info->se_info.atr[i]);
+ if (ST_NCI_ATR_TA_PRESENT(td))
+ i++;
+ if (ST_NCI_ATR_TB_PRESENT(td)) {
+ i++;
+ return info->se_info.atr[i] >> 4;
+ }
+ }
+ return ST_NCI_ATR_DEFAULT_BWI;
+}
+
+static void st_nci_se_get_atr(struct nci_dev *ndev)
+{
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+ int r;
+ struct sk_buff *skb;
+
+ r = nci_hci_get_param(ndev, ST_NCI_APDU_READER_GATE,
+ NCI_HCI_APDU_PARAM_ATR, &skb);
+ if (r < 0)
+ return;
+
+ if (skb->len <= ST_NCI_ESE_MAX_LENGTH) {
+ memcpy(info->se_info.atr, skb->data, skb->len);
+
+ info->se_info.wt_timeout =
+ ST_NCI_BWI_TO_TIMEOUT(st_nci_se_get_bwi(ndev));
+ }
+ kfree_skb(skb);
+}
+
+int st_nci_hci_load_session(struct nci_dev *ndev)
+{
+ int i, j, r;
+ struct sk_buff *skb_pipe_list, *skb_pipe_info;
+ struct st_nci_pipe_info *dm_pipe_info;
+ u8 pipe_list[] = { ST_NCI_DM_GETINFO_PIPE_LIST,
+ ST_NCI_TERMINAL_HOST_ID};
+ u8 pipe_info[] = { ST_NCI_DM_GETINFO_PIPE_INFO,
+ ST_NCI_TERMINAL_HOST_ID, 0};
+
+ /* On ST_NCI device pipes number are dynamics
+ * If pipes are already created, hci_dev_up will fail.
+ * Doing a clear all pipe is a bad idea because:
+ * - It does useless EEPROM cycling
+ * - It might cause issue for secure elements support
+ * (such as removing connectivity or APDU reader pipe)
+ * A better approach on ST_NCI is to:
+ * - get a pipe list for each host.
+ * (eg: ST_NCI_HOST_CONTROLLER_ID for now).
+ * (TODO Later on UICC HOST and eSE HOST)
+ * - get pipe information
+ * - match retrieved pipe list in st_nci_gates
+ * ST_NCI_DEVICE_MGNT_GATE is a proprietary gate
+ * with ST_NCI_DEVICE_MGNT_PIPE.
+ * Pipe can be closed and need to be open.
+ */
+ r = nci_hci_connect_gate(ndev, ST_NCI_HOST_CONTROLLER_ID,
+ ST_NCI_DEVICE_MGNT_GATE,
+ ST_NCI_DEVICE_MGNT_PIPE);
+ if (r < 0)
+ return r;
+
+ /* Get pipe list */
+ r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
+ ST_NCI_DM_GETINFO, pipe_list, sizeof(pipe_list),
+ &skb_pipe_list);
+ if (r < 0)
+ return r;
+
+ /* Complete the existing gate_pipe table */
+ for (i = 0; i < skb_pipe_list->len; i++) {
+ pipe_info[2] = skb_pipe_list->data[i];
+ r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
+ ST_NCI_DM_GETINFO, pipe_info,
+ sizeof(pipe_info), &skb_pipe_info);
+
+ if (r)
+ continue;
+
+ /*
+ * Match pipe ID and gate ID
+ * Output format from ST21NFC_DM_GETINFO is:
+ * - pipe state (1byte)
+ * - source hid (1byte)
+ * - source gid (1byte)
+ * - destination hid (1byte)
+ * - destination gid (1byte)
+ */
+ dm_pipe_info = (struct st_nci_pipe_info *)skb_pipe_info->data;
+ if (dm_pipe_info->dst_gate_id == ST_NCI_APDU_READER_GATE &&
+ dm_pipe_info->src_host_id == ST_NCI_UICC_HOST_ID) {
+ pr_err("Unexpected apdu_reader pipe on host %x\n",
+ dm_pipe_info->src_host_id);
+ kfree_skb(skb_pipe_info);
+ continue;
+ }
+
+ for (j = 3; (j < ARRAY_SIZE(st_nci_gates)) &&
+ (st_nci_gates[j].gate != dm_pipe_info->dst_gate_id); j++)
+ ;
+
+ if (j < ARRAY_SIZE(st_nci_gates) &&
+ st_nci_gates[j].gate == dm_pipe_info->dst_gate_id &&
+ ST_NCI_DM_IS_PIPE_OPEN(dm_pipe_info->pipe_state)) {
+ ndev->hci_dev->init_data.gates[j].pipe = pipe_info[2];
+
+ ndev->hci_dev->gate2pipe[st_nci_gates[j].gate] =
+ pipe_info[2];
+ ndev->hci_dev->pipes[pipe_info[2]].gate =
+ st_nci_gates[j].gate;
+ ndev->hci_dev->pipes[pipe_info[2]].host =
+ dm_pipe_info->src_host_id;
+ }
+ kfree_skb(skb_pipe_info);
+ }
+
+ /*
+ * 3 gates have a well known pipe ID. Only NCI_HCI_LINK_MGMT_GATE
+ * is not yet open at this stage.
+ */
+ r = nci_hci_connect_gate(ndev, ST_NCI_HOST_CONTROLLER_ID,
+ NCI_HCI_LINK_MGMT_GATE,
+ NCI_HCI_LINK_MGMT_PIPE);
+
+ kfree_skb(skb_pipe_list);
+ return r;
+}
+EXPORT_SYMBOL_GPL(st_nci_hci_load_session);
+
+static void st_nci_hci_admin_event_received(struct nci_dev *ndev,
+ u8 event, struct sk_buff *skb)
+{
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+
+ switch (event) {
+ case ST_NCI_EVT_HOT_PLUG:
+ if (info->se_info.se_active) {
+ if (!ST_NCI_EVT_HOT_PLUG_IS_INHIBITED(skb)) {
+ del_timer_sync(&info->se_info.se_active_timer);
+ info->se_info.se_active = false;
+ complete(&info->se_info.req_completion);
+ } else {
+ mod_timer(&info->se_info.se_active_timer,
+ jiffies +
+ msecs_to_jiffies(ST_NCI_SE_TO_PIPES));
+ }
+ }
+ break;
+ default:
+ nfc_err(&ndev->nfc_dev->dev, "Unexpected event on admin gate\n");
+ }
+}
+
+static int st_nci_hci_apdu_reader_event_received(struct nci_dev *ndev,
+ u8 event,
+ struct sk_buff *skb)
+{
+ int r = 0;
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+
+ pr_debug("apdu reader gate event: %x\n", event);
+
+ switch (event) {
+ case ST_NCI_EVT_TRANSMIT_DATA:
+ del_timer_sync(&info->se_info.bwi_timer);
+ info->se_info.bwi_active = false;
+ info->se_info.cb(info->se_info.cb_context,
+ skb->data, skb->len, 0);
+ break;
+ case ST_NCI_EVT_WTX_REQUEST:
+ mod_timer(&info->se_info.bwi_timer, jiffies +
+ msecs_to_jiffies(info->se_info.wt_timeout));
+ break;
+ default:
+ nfc_err(&ndev->nfc_dev->dev, "Unexpected event on apdu reader gate\n");
+ return 1;
+ }
+
+ kfree_skb(skb);
+ return r;
+}
+
+/*
+ * Returns:
+ * <= 0: driver handled the event, skb consumed
+ * 1: driver does not handle the event, please do standard processing
+ */
+static int st_nci_hci_connectivity_event_received(struct nci_dev *ndev,
+ u8 host, u8 event,
+ struct sk_buff *skb)
+{
+ int r = 0;
+ struct device *dev = &ndev->nfc_dev->dev;
+ struct nfc_evt_transaction *transaction;
+
+ pr_debug("connectivity gate event: %x\n", event);
+
+ switch (event) {
+ case ST_NCI_EVT_CONNECTIVITY:
+ r = nfc_se_connectivity(ndev->nfc_dev, host);
+ break;
+ case ST_NCI_EVT_TRANSACTION:
+ /* According to specification etsi 102 622
+ * 11.2.2.4 EVT_TRANSACTION Table 52
+ * Description Tag Length
+ * AID 81 5 to 16
+ * PARAMETERS 82 0 to 255
+ */
+ if (skb->len < NFC_MIN_AID_LENGTH + 2 &&
+ skb->data[0] != NFC_EVT_TRANSACTION_AID_TAG)
+ return -EPROTO;
+
+ transaction = (struct nfc_evt_transaction *)devm_kzalloc(dev,
+ skb->len - 2, GFP_KERNEL);
+ if (!transaction)
+ return -ENOMEM;
+
+ transaction->aid_len = skb->data[1];
+ memcpy(transaction->aid, &skb->data[2], transaction->aid_len);
+
+ /* Check next byte is PARAMETERS tag (82) */
+ if (skb->data[transaction->aid_len + 2] !=
+ NFC_EVT_TRANSACTION_PARAMS_TAG)
+ return -EPROTO;
+
+ transaction->params_len = skb->data[transaction->aid_len + 3];
+ memcpy(transaction->params, skb->data +
+ transaction->aid_len + 4, transaction->params_len);
+
+ r = nfc_se_transaction(ndev->nfc_dev, host, transaction);
+ break;
+ default:
+ nfc_err(&ndev->nfc_dev->dev, "Unexpected event on connectivity gate\n");
+ return 1;
+ }
+ kfree_skb(skb);
+ return r;
+}
+
+void st_nci_hci_event_received(struct nci_dev *ndev, u8 pipe,
+ u8 event, struct sk_buff *skb)
+{
+ u8 gate = ndev->hci_dev->pipes[pipe].gate;
+ u8 host = ndev->hci_dev->pipes[pipe].host;
+
+ switch (gate) {
+ case NCI_HCI_ADMIN_GATE:
+ st_nci_hci_admin_event_received(ndev, event, skb);
+ break;
+ case ST_NCI_APDU_READER_GATE:
+ st_nci_hci_apdu_reader_event_received(ndev, event, skb);
+ break;
+ case ST_NCI_CONNECTIVITY_GATE:
+ st_nci_hci_connectivity_event_received(ndev, host, event, skb);
+ break;
+ }
+}
+EXPORT_SYMBOL_GPL(st_nci_hci_event_received);
+
+void st_nci_hci_cmd_received(struct nci_dev *ndev, u8 pipe, u8 cmd,
+ struct sk_buff *skb)
+{
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+ u8 gate = ndev->hci_dev->pipes[pipe].gate;
+
+ pr_debug("cmd: %x\n", cmd);
+
+ switch (cmd) {
+ case NCI_HCI_ANY_OPEN_PIPE:
+ if (gate != ST_NCI_APDU_READER_GATE &&
+ ndev->hci_dev->pipes[pipe].host != ST_NCI_UICC_HOST_ID)
+ ndev->hci_dev->count_pipes++;
+
+ if (ndev->hci_dev->count_pipes ==
+ ndev->hci_dev->expected_pipes) {
+ del_timer_sync(&info->se_info.se_active_timer);
+ info->se_info.se_active = false;
+ ndev->hci_dev->count_pipes = 0;
+ complete(&info->se_info.req_completion);
+ }
+ break;
+ }
+}
+EXPORT_SYMBOL_GPL(st_nci_hci_cmd_received);
+
+static int st_nci_control_se(struct nci_dev *ndev, u8 se_idx,
+ u8 state)
+{
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+ int r, i;
+ struct sk_buff *sk_host_list;
+ u8 host_id;
+
+ switch (se_idx) {
+ case ST_NCI_UICC_HOST_ID:
+ ndev->hci_dev->count_pipes = 0;
+ ndev->hci_dev->expected_pipes = ST_NCI_SE_COUNT_PIPE_UICC;
+ break;
+ case ST_NCI_ESE_HOST_ID:
+ ndev->hci_dev->count_pipes = 0;
+ ndev->hci_dev->expected_pipes = ST_NCI_SE_COUNT_PIPE_EMBEDDED;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Wait for an EVT_HOT_PLUG in order to
+ * retrieve a relevant host list.
+ */
+ reinit_completion(&info->se_info.req_completion);
+ r = nci_nfcee_mode_set(ndev, se_idx, state);
+ if (r != NCI_STATUS_OK)
+ return r;
+
+ mod_timer(&info->se_info.se_active_timer, jiffies +
+ msecs_to_jiffies(ST_NCI_SE_TO_HOT_PLUG));
+ info->se_info.se_active = true;
+
+ /* Ignore return value and check in any case the host_list */
+ wait_for_completion_interruptible(&info->se_info.req_completion);
+
+ /* There might be some "collision" after receiving a HOT_PLUG event
+ * This may cause the CLF to not answer to the next hci command.
+ * There is no possible synchronization to prevent this.
+ * Adding a small delay is the only way to solve the issue.
+ */
+ if (info->se_info.se_status->is_ese_present &&
+ info->se_info.se_status->is_uicc_present)
+ usleep_range(15000, 20000);
+
+ r = nci_hci_get_param(ndev, NCI_HCI_ADMIN_GATE,
+ NCI_HCI_ADMIN_PARAM_HOST_LIST, &sk_host_list);
+ if (r != NCI_HCI_ANY_OK)
+ return r;
+
+ for (i = 0; i < sk_host_list->len &&
+ sk_host_list->data[i] != se_idx; i++)
+ ;
+ host_id = sk_host_list->data[i];
+ kfree_skb(sk_host_list);
+ if (state == ST_NCI_SE_MODE_ON && host_id == se_idx)
+ return se_idx;
+ else if (state == ST_NCI_SE_MODE_OFF && host_id != se_idx)
+ return se_idx;
+
+ return -1;
+}
+
+int st_nci_disable_se(struct nci_dev *ndev, u32 se_idx)
+{
+ int r;
+
+ pr_debug("st_nci_disable_se\n");
+
+ /*
+ * According to upper layer, se_idx == NFC_SE_UICC when
+ * info->se_info.se_status->is_uicc_enable is true should never happen
+ * Same for eSE.
+ */
+ r = st_nci_control_se(ndev, se_idx, ST_NCI_SE_MODE_OFF);
+ if (r < 0) {
+ /* Do best effort to release SWP */
+ if (se_idx == NFC_SE_EMBEDDED) {
+ r = nci_hci_send_event(ndev, ST_NCI_APDU_READER_GATE,
+ ST_NCI_EVT_SE_END_OF_APDU_TRANSFER,
+ NULL, 0);
+ }
+ return r;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(st_nci_disable_se);
+
+int st_nci_enable_se(struct nci_dev *ndev, u32 se_idx)
+{
+ int r;
+
+ pr_debug("st_nci_enable_se\n");
+
+ /*
+ * According to upper layer, se_idx == NFC_SE_UICC when
+ * info->se_info.se_status->is_uicc_enable is true should never happen.
+ * Same for eSE.
+ */
+ r = st_nci_control_se(ndev, se_idx, ST_NCI_SE_MODE_ON);
+ if (r == ST_NCI_ESE_HOST_ID) {
+ st_nci_se_get_atr(ndev);
+ r = nci_hci_send_event(ndev, ST_NCI_APDU_READER_GATE,
+ ST_NCI_EVT_SE_SOFT_RESET, NULL, 0);
+ }
+
+ if (r < 0) {
+ /*
+ * The activation procedure failed, the secure element
+ * is not connected. Remove from the list.
+ */
+ nfc_remove_se(ndev->nfc_dev, se_idx);
+ return r;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(st_nci_enable_se);
+
+static int st_nci_hci_network_init(struct nci_dev *ndev)
+{
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+ struct core_conn_create_dest_spec_params *dest_params;
+ struct dest_spec_params spec_params;
+ struct nci_conn_info *conn_info;
+ int r, dev_num;
+
+ dest_params =
+ kzalloc(sizeof(struct core_conn_create_dest_spec_params) +
+ sizeof(struct dest_spec_params), GFP_KERNEL);
+ if (dest_params == NULL) {
+ r = -ENOMEM;
+ goto exit;
+ }
+
+ dest_params->type = NCI_DESTINATION_SPECIFIC_PARAM_NFCEE_TYPE;
+ dest_params->length = sizeof(struct dest_spec_params);
+ spec_params.id = ndev->hci_dev->nfcee_id;
+ spec_params.protocol = NCI_NFCEE_INTERFACE_HCI_ACCESS;
+ memcpy(dest_params->value, &spec_params,
+ sizeof(struct dest_spec_params));
+ r = nci_core_conn_create(ndev, NCI_DESTINATION_NFCEE, 1,
+ sizeof(struct core_conn_create_dest_spec_params) +
+ sizeof(struct dest_spec_params),
+ dest_params);
+ if (r != NCI_STATUS_OK)
+ goto free_dest_params;
+
+ conn_info = ndev->hci_dev->conn_info;
+ if (!conn_info)
+ goto free_dest_params;
+
+ ndev->hci_dev->init_data.gate_count = ARRAY_SIZE(st_nci_gates);
+ memcpy(ndev->hci_dev->init_data.gates, st_nci_gates,
+ sizeof(st_nci_gates));
+
+ /*
+ * Session id must include the driver name + i2c bus addr
+ * persistent info to discriminate 2 identical chips
+ */
+ dev_num = find_first_zero_bit(dev_mask, ST_NCI_NUM_DEVICES);
+ if (dev_num >= ST_NCI_NUM_DEVICES) {
+ r = -ENODEV;
+ goto free_dest_params;
+ }
+
+ scnprintf(ndev->hci_dev->init_data.session_id,
+ sizeof(ndev->hci_dev->init_data.session_id),
+ "%s%2x", "ST21BH", dev_num);
+
+ r = nci_hci_dev_session_init(ndev);
+ if (r != NCI_HCI_ANY_OK)
+ goto free_dest_params;
+
+ /*
+ * In factory mode, we prevent secure elements activation
+ * by disabling nfcee on the current HCI connection id.
+ * HCI will be used here only for proprietary commands.
+ */
+ if (test_bit(ST_NCI_FACTORY_MODE, &info->flags))
+ r = nci_nfcee_mode_set(ndev,
+ ndev->hci_dev->conn_info->dest_params->id,
+ NCI_NFCEE_DISABLE);
+ else
+ r = nci_nfcee_mode_set(ndev,
+ ndev->hci_dev->conn_info->dest_params->id,
+ NCI_NFCEE_ENABLE);
+
+free_dest_params:
+ kfree(dest_params);
+
+exit:
+ return r;
+}
+
+int st_nci_discover_se(struct nci_dev *ndev)
+{
+ u8 white_list[2];
+ int r, wl_size = 0;
+ int se_count = 0;
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+
+ pr_debug("st_nci_discover_se\n");
+
+ r = st_nci_hci_network_init(ndev);
+ if (r != 0)
+ return r;
+
+ if (test_bit(ST_NCI_FACTORY_MODE, &info->flags))
+ return 0;
+
+ if (info->se_info.se_status->is_uicc_present)
+ white_list[wl_size++] = ST_NCI_UICC_HOST_ID;
+ if (info->se_info.se_status->is_ese_present)
+ white_list[wl_size++] = ST_NCI_ESE_HOST_ID;
+
+ if (wl_size) {
+ r = nci_hci_set_param(ndev, NCI_HCI_ADMIN_GATE,
+ NCI_HCI_ADMIN_PARAM_WHITELIST,
+ white_list, wl_size);
+ if (r != NCI_HCI_ANY_OK)
+ return r;
+ }
+
+ if (info->se_info.se_status->is_uicc_present) {
+ nfc_add_se(ndev->nfc_dev, ST_NCI_UICC_HOST_ID, NFC_SE_UICC);
+ se_count++;
+ }
+
+ if (info->se_info.se_status->is_ese_present) {
+ nfc_add_se(ndev->nfc_dev, ST_NCI_ESE_HOST_ID, NFC_SE_EMBEDDED);
+ se_count++;
+ }
+
+ return !se_count;
+}
+EXPORT_SYMBOL_GPL(st_nci_discover_se);
+
+int st_nci_se_io(struct nci_dev *ndev, u32 se_idx,
+ u8 *apdu, size_t apdu_length,
+ se_io_cb_t cb, void *cb_context)
+{
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+
+ pr_debug("\n");
+
+ switch (se_idx) {
+ case ST_NCI_ESE_HOST_ID:
+ info->se_info.cb = cb;
+ info->se_info.cb_context = cb_context;
+ mod_timer(&info->se_info.bwi_timer, jiffies +
+ msecs_to_jiffies(info->se_info.wt_timeout));
+ info->se_info.bwi_active = true;
+ return nci_hci_send_event(ndev, ST_NCI_APDU_READER_GATE,
+ ST_NCI_EVT_TRANSMIT_DATA, apdu,
+ apdu_length);
+ default:
+ return -ENODEV;
+ }
+}
+EXPORT_SYMBOL(st_nci_se_io);
+
+static void st_nci_se_wt_timeout(struct timer_list *t)
+{
+ /*
+ * No answer from the secure element
+ * within the defined timeout.
+ * Let's send a reset request as recovery procedure.
+ * According to the situation, we first try to send a software reset
+ * to the secure element. If the next command is still not
+ * answering in time, we send to the CLF a secure element hardware
+ * reset request.
+ */
+ /* hardware reset managed through VCC_UICC_OUT power supply */
+ u8 param = 0x01;
+ struct st_nci_info *info = from_timer(info, t, se_info.bwi_timer);
+
+ pr_debug("\n");
+
+ info->se_info.bwi_active = false;
+
+ if (!info->se_info.xch_error) {
+ info->se_info.xch_error = true;
+ nci_hci_send_event(info->ndlc->ndev, ST_NCI_APDU_READER_GATE,
+ ST_NCI_EVT_SE_SOFT_RESET, NULL, 0);
+ } else {
+ info->se_info.xch_error = false;
+ nci_hci_send_event(info->ndlc->ndev, ST_NCI_DEVICE_MGNT_GATE,
+ ST_NCI_EVT_SE_HARD_RESET, &param, 1);
+ }
+ info->se_info.cb(info->se_info.cb_context, NULL, 0, -ETIME);
+}
+
+static void st_nci_se_activation_timeout(struct timer_list *t)
+{
+ struct st_nci_info *info = from_timer(info, t,
+ se_info.se_active_timer);
+
+ pr_debug("\n");
+
+ info->se_info.se_active = false;
+
+ complete(&info->se_info.req_completion);
+}
+
+int st_nci_se_init(struct nci_dev *ndev, struct st_nci_se_status *se_status)
+{
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+
+ init_completion(&info->se_info.req_completion);
+ /* initialize timers */
+ timer_setup(&info->se_info.bwi_timer, st_nci_se_wt_timeout, 0);
+ info->se_info.bwi_active = false;
+
+ timer_setup(&info->se_info.se_active_timer,
+ st_nci_se_activation_timeout, 0);
+ info->se_info.se_active = false;
+
+ info->se_info.xch_error = false;
+
+ info->se_info.wt_timeout =
+ ST_NCI_BWI_TO_TIMEOUT(ST_NCI_ATR_DEFAULT_BWI);
+
+ info->se_info.se_status = se_status;
+
+ return 0;
+}
+EXPORT_SYMBOL(st_nci_se_init);
+
+void st_nci_se_deinit(struct nci_dev *ndev)
+{
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+
+ if (info->se_info.bwi_active)
+ del_timer_sync(&info->se_info.bwi_timer);
+ if (info->se_info.se_active)
+ del_timer_sync(&info->se_info.se_active_timer);
+
+ info->se_info.se_active = false;
+ info->se_info.bwi_active = false;
+}
+EXPORT_SYMBOL(st_nci_se_deinit);
+
diff --git a/drivers/nfc/st-nci/spi.c b/drivers/nfc/st-nci/spi.c
new file mode 100644
index 000000000..14705591b
--- /dev/null
+++ b/drivers/nfc/st-nci/spi.c
@@ -0,0 +1,329 @@
+/*
+ * SPI Link Layer for ST NCI based Driver
+ * Copyright (C) 2014-2015 STMicroelectronics SAS. 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/>.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/gpio/consumer.h>
+#include <linux/acpi.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/nfc.h>
+#include <linux/of.h>
+#include <net/nfc/nci.h>
+
+#include "st-nci.h"
+
+#define DRIVER_DESC "NCI NFC driver for ST_NCI"
+
+/* ndlc header */
+#define ST_NCI_FRAME_HEADROOM 1
+#define ST_NCI_FRAME_TAILROOM 0
+
+#define ST_NCI_SPI_MIN_SIZE 4 /* PCB(1) + NCI Packet header(3) */
+#define ST_NCI_SPI_MAX_SIZE 250 /* req 4.2.1 */
+
+#define ST_NCI_DRIVER_NAME "st_nci"
+#define ST_NCI_SPI_DRIVER_NAME "st_nci_spi"
+
+struct st_nci_spi_phy {
+ struct spi_device *spi_dev;
+ struct llt_ndlc *ndlc;
+
+ bool irq_active;
+
+ struct gpio_desc *gpiod_reset;
+
+ struct st_nci_se_status se_status;
+};
+
+static int st_nci_spi_enable(void *phy_id)
+{
+ struct st_nci_spi_phy *phy = phy_id;
+
+ gpiod_set_value(phy->gpiod_reset, 0);
+ usleep_range(10000, 15000);
+ gpiod_set_value(phy->gpiod_reset, 1);
+ usleep_range(80000, 85000);
+
+ if (phy->ndlc->powered == 0 && phy->irq_active == 0) {
+ enable_irq(phy->spi_dev->irq);
+ phy->irq_active = true;
+ }
+
+ return 0;
+}
+
+static void st_nci_spi_disable(void *phy_id)
+{
+ struct st_nci_spi_phy *phy = phy_id;
+
+ disable_irq_nosync(phy->spi_dev->irq);
+ phy->irq_active = false;
+}
+
+/*
+ * Writing a frame must not return the number of written bytes.
+ * It must return either zero for success, or <0 for error.
+ * In addition, it must not alter the skb
+ */
+static int st_nci_spi_write(void *phy_id, struct sk_buff *skb)
+{
+ int r;
+ struct st_nci_spi_phy *phy = phy_id;
+ struct spi_device *dev = phy->spi_dev;
+ struct sk_buff *skb_rx;
+ u8 buf[ST_NCI_SPI_MAX_SIZE + NCI_DATA_HDR_SIZE +
+ ST_NCI_FRAME_HEADROOM + ST_NCI_FRAME_TAILROOM];
+ struct spi_transfer spi_xfer = {
+ .tx_buf = skb->data,
+ .rx_buf = buf,
+ .len = skb->len,
+ };
+
+ if (phy->ndlc->hard_fault != 0)
+ return phy->ndlc->hard_fault;
+
+ r = spi_sync_transfer(dev, &spi_xfer, 1);
+ /*
+ * We may have received some valuable data on miso line.
+ * Send them back in the ndlc state machine.
+ */
+ if (!r) {
+ skb_rx = alloc_skb(skb->len, GFP_KERNEL);
+ if (!skb_rx) {
+ r = -ENOMEM;
+ goto exit;
+ }
+
+ skb_put(skb_rx, skb->len);
+ memcpy(skb_rx->data, buf, skb->len);
+ ndlc_recv(phy->ndlc, skb_rx);
+ }
+
+exit:
+ return r;
+}
+
+/*
+ * Reads an ndlc frame and returns it in a newly allocated sk_buff.
+ * returns:
+ * 0 : if received frame is complete
+ * -EREMOTEIO : i2c read error (fatal)
+ * -EBADMSG : frame was incorrect and discarded
+ * -ENOMEM : cannot allocate skb, frame dropped
+ */
+static int st_nci_spi_read(struct st_nci_spi_phy *phy,
+ struct sk_buff **skb)
+{
+ int r;
+ u8 len;
+ u8 buf[ST_NCI_SPI_MAX_SIZE];
+ struct spi_device *dev = phy->spi_dev;
+ struct spi_transfer spi_xfer = {
+ .rx_buf = buf,
+ .len = ST_NCI_SPI_MIN_SIZE,
+ };
+
+ r = spi_sync_transfer(dev, &spi_xfer, 1);
+ if (r < 0)
+ return -EREMOTEIO;
+
+ len = be16_to_cpu(*(__be16 *) (buf + 2));
+ if (len > ST_NCI_SPI_MAX_SIZE) {
+ nfc_err(&dev->dev, "invalid frame len\n");
+ phy->ndlc->hard_fault = 1;
+ return -EBADMSG;
+ }
+
+ *skb = alloc_skb(ST_NCI_SPI_MIN_SIZE + len, GFP_KERNEL);
+ if (*skb == NULL)
+ return -ENOMEM;
+
+ skb_reserve(*skb, ST_NCI_SPI_MIN_SIZE);
+ skb_put(*skb, ST_NCI_SPI_MIN_SIZE);
+ memcpy((*skb)->data, buf, ST_NCI_SPI_MIN_SIZE);
+
+ if (!len)
+ return 0;
+
+ spi_xfer.len = len;
+ r = spi_sync_transfer(dev, &spi_xfer, 1);
+ if (r < 0) {
+ kfree_skb(*skb);
+ return -EREMOTEIO;
+ }
+
+ skb_put(*skb, len);
+ memcpy((*skb)->data + ST_NCI_SPI_MIN_SIZE, buf, len);
+
+ return 0;
+}
+
+/*
+ * Reads an ndlc frame from the chip.
+ *
+ * On ST21NFCB, IRQ goes in idle state when read starts.
+ */
+static irqreturn_t st_nci_irq_thread_fn(int irq, void *phy_id)
+{
+ struct st_nci_spi_phy *phy = phy_id;
+ struct spi_device *dev;
+ struct sk_buff *skb = NULL;
+ int r;
+
+ if (!phy || !phy->ndlc || irq != phy->spi_dev->irq) {
+ WARN_ON_ONCE(1);
+ return IRQ_NONE;
+ }
+
+ dev = phy->spi_dev;
+ dev_dbg(&dev->dev, "IRQ\n");
+
+ if (phy->ndlc->hard_fault)
+ return IRQ_HANDLED;
+
+ if (!phy->ndlc->powered) {
+ st_nci_spi_disable(phy);
+ return IRQ_HANDLED;
+ }
+
+ r = st_nci_spi_read(phy, &skb);
+ if (r == -EREMOTEIO || r == -ENOMEM || r == -EBADMSG)
+ return IRQ_HANDLED;
+
+ ndlc_recv(phy->ndlc, skb);
+
+ return IRQ_HANDLED;
+}
+
+static struct nfc_phy_ops spi_phy_ops = {
+ .write = st_nci_spi_write,
+ .enable = st_nci_spi_enable,
+ .disable = st_nci_spi_disable,
+};
+
+static const struct acpi_gpio_params reset_gpios = { 1, 0, false };
+
+static const struct acpi_gpio_mapping acpi_st_nci_gpios[] = {
+ { "reset-gpios", &reset_gpios, 1 },
+ {},
+};
+
+static int st_nci_spi_probe(struct spi_device *dev)
+{
+ struct st_nci_spi_phy *phy;
+ int r;
+
+ dev_dbg(&dev->dev, "%s\n", __func__);
+ dev_dbg(&dev->dev, "IRQ: %d\n", dev->irq);
+
+ /* Check SPI platform functionnalities */
+ if (!dev) {
+ pr_debug("%s: dev is NULL. Device is not accessible.\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ phy = devm_kzalloc(&dev->dev, sizeof(struct st_nci_spi_phy),
+ GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ phy->spi_dev = dev;
+
+ spi_set_drvdata(dev, phy);
+
+ r = devm_acpi_dev_add_driver_gpios(&dev->dev, acpi_st_nci_gpios);
+ if (r)
+ dev_dbg(&dev->dev, "Unable to add GPIO mapping table\n");
+
+ /* Get RESET GPIO */
+ phy->gpiod_reset = devm_gpiod_get(&dev->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(phy->gpiod_reset)) {
+ nfc_err(&dev->dev, "Unable to get RESET GPIO\n");
+ return PTR_ERR(phy->gpiod_reset);
+ }
+
+ phy->se_status.is_ese_present =
+ device_property_read_bool(&dev->dev, "ese-present");
+ phy->se_status.is_uicc_present =
+ device_property_read_bool(&dev->dev, "uicc-present");
+
+ r = ndlc_probe(phy, &spi_phy_ops, &dev->dev,
+ ST_NCI_FRAME_HEADROOM, ST_NCI_FRAME_TAILROOM,
+ &phy->ndlc, &phy->se_status);
+ if (r < 0) {
+ nfc_err(&dev->dev, "Unable to register ndlc layer\n");
+ return r;
+ }
+
+ phy->irq_active = true;
+ r = devm_request_threaded_irq(&dev->dev, dev->irq, NULL,
+ st_nci_irq_thread_fn,
+ IRQF_ONESHOT,
+ ST_NCI_SPI_DRIVER_NAME, phy);
+ if (r < 0)
+ nfc_err(&dev->dev, "Unable to register IRQ handler\n");
+
+ return r;
+}
+
+static int st_nci_spi_remove(struct spi_device *dev)
+{
+ struct st_nci_spi_phy *phy = spi_get_drvdata(dev);
+
+ dev_dbg(&dev->dev, "%s\n", __func__);
+
+ ndlc_remove(phy->ndlc);
+
+ return 0;
+}
+
+static struct spi_device_id st_nci_spi_id_table[] = {
+ {ST_NCI_SPI_DRIVER_NAME, 0},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, st_nci_spi_id_table);
+
+static const struct acpi_device_id st_nci_spi_acpi_match[] = {
+ {"SMO2101", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, st_nci_spi_acpi_match);
+
+static const struct of_device_id of_st_nci_spi_match[] = {
+ { .compatible = "st,st21nfcb-spi", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, of_st_nci_spi_match);
+
+static struct spi_driver st_nci_spi_driver = {
+ .driver = {
+ .name = ST_NCI_SPI_DRIVER_NAME,
+ .of_match_table = of_match_ptr(of_st_nci_spi_match),
+ .acpi_match_table = ACPI_PTR(st_nci_spi_acpi_match),
+ },
+ .probe = st_nci_spi_probe,
+ .id_table = st_nci_spi_id_table,
+ .remove = st_nci_spi_remove,
+};
+module_spi_driver(st_nci_spi_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION(DRIVER_DESC);
diff --git a/drivers/nfc/st-nci/st-nci.h b/drivers/nfc/st-nci/st-nci.h
new file mode 100644
index 000000000..afaf138b7
--- /dev/null
+++ b/drivers/nfc/st-nci/st-nci.h
@@ -0,0 +1,153 @@
+/*
+ * NCI based Driver for STMicroelectronics NFC Chip
+ *
+ * Copyright (C) 2014 STMicroelectronics SAS. 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_ST_NCI_H_
+#define __LOCAL_ST_NCI_H_
+
+#include "ndlc.h"
+
+/* Define private flags: */
+#define ST_NCI_RUNNING 1
+
+#define ST_NCI_CORE_PROP 0x01
+#define ST_NCI_SET_NFC_MODE 0x02
+
+/*
+ * ref ISO7816-3 chap 8.1. the initial character TS is followed by a
+ * sequence of at most 32 characters.
+ */
+#define ST_NCI_ESE_MAX_LENGTH 33
+
+#define ST_NCI_DEVICE_MGNT_GATE 0x01
+
+#define ST_NCI_VENDOR_OUI 0x0080E1 /* STMicroelectronics */
+#define ST_NCI_FACTORY_MODE 2
+
+struct nci_mode_set_cmd {
+ u8 cmd_type;
+ u8 mode;
+} __packed;
+
+struct nci_mode_set_rsp {
+ u8 status;
+} __packed;
+
+struct st_nci_se_status {
+ bool is_ese_present;
+ bool is_uicc_present;
+};
+
+struct st_nci_se_info {
+ struct st_nci_se_status *se_status;
+ u8 atr[ST_NCI_ESE_MAX_LENGTH];
+ struct completion req_completion;
+
+ struct timer_list bwi_timer;
+ int wt_timeout; /* in msecs */
+ bool bwi_active;
+
+ struct timer_list se_active_timer;
+ bool se_active;
+
+ bool xch_error;
+
+ se_io_cb_t cb;
+ void *cb_context;
+};
+
+/**
+ * enum nfc_vendor_cmds - supported nfc vendor commands
+ *
+ * @FACTORY_MODE: Allow to set the driver into a mode where no secure element
+ * are activated. It does not consider any NFC_ATTR_VENDOR_DATA.
+ * @HCI_CLEAR_ALL_PIPES: Allow to execute a HCI clear all pipes command.
+ * It does not consider any NFC_ATTR_VENDOR_DATA.
+ * @HCI_DM_PUT_DATA: Allow to configure specific CLF registry as for example
+ * RF trimmings or low level drivers configurations (I2C, SPI, SWP).
+ * @HCI_DM_UPDATE_AID: Allow to configure an AID routing into the CLF routing
+ * table following RF technology, CLF mode or protocol.
+ * @HCI_DM_GET_INFO: Allow to retrieve CLF information.
+ * @HCI_DM_GET_DATA: Allow to retrieve CLF configurable data such as low
+ * level drivers configurations or RF trimmings.
+ * @HCI_DM_DIRECT_LOAD: Allow to load a firmware into the CLF. A complete
+ * packet can be more than 8KB.
+ * @HCI_DM_RESET: Allow to run a CLF reset in order to "commit" CLF
+ * configuration changes without CLF power off.
+ * @HCI_GET_PARAM: Allow to retrieve an HCI CLF parameter (for example the
+ * white list).
+ * @HCI_DM_FIELD_GENERATOR: Allow to generate different kind of RF
+ * technology. When using this command to anti-collision is done.
+ * @LOOPBACK: Allow to echo a command and test the Dh to CLF connectivity.
+ * @HCI_DM_VDC_MEASUREMENT_VALUE: Allow to measure the field applied on the
+ * CLF antenna. A value between 0 and 0x0f is returned. 0 is maximum.
+ * @HCI_DM_FWUPD_START: Allow to put CLF into firmware update mode. It is a
+ * specific CLF command as there is no GPIO for this.
+ * @HCI_DM_FWUPD_END: Allow to complete firmware update.
+ * @HCI_DM_VDC_VALUE_COMPARISON: Allow to compare the field applied on the
+ * CLF antenna to a reference value.
+ * @MANUFACTURER_SPECIFIC: Allow to retrieve manufacturer specific data
+ * received during a NCI_CORE_INIT_CMD.
+ */
+enum nfc_vendor_cmds {
+ FACTORY_MODE,
+ HCI_CLEAR_ALL_PIPES,
+ HCI_DM_PUT_DATA,
+ HCI_DM_UPDATE_AID,
+ HCI_DM_GET_INFO,
+ HCI_DM_GET_DATA,
+ HCI_DM_DIRECT_LOAD,
+ HCI_DM_RESET,
+ HCI_GET_PARAM,
+ HCI_DM_FIELD_GENERATOR,
+ LOOPBACK,
+ HCI_DM_FWUPD_START,
+ HCI_DM_FWUPD_END,
+ HCI_DM_VDC_MEASUREMENT_VALUE,
+ HCI_DM_VDC_VALUE_COMPARISON,
+ MANUFACTURER_SPECIFIC,
+};
+
+struct st_nci_info {
+ struct llt_ndlc *ndlc;
+ unsigned long flags;
+
+ struct st_nci_se_info se_info;
+};
+
+void st_nci_remove(struct nci_dev *ndev);
+int st_nci_probe(struct llt_ndlc *ndlc, int phy_headroom,
+ int phy_tailroom, struct st_nci_se_status *se_status);
+
+int st_nci_se_init(struct nci_dev *ndev, struct st_nci_se_status *se_status);
+void st_nci_se_deinit(struct nci_dev *ndev);
+
+int st_nci_discover_se(struct nci_dev *ndev);
+int st_nci_enable_se(struct nci_dev *ndev, u32 se_idx);
+int st_nci_disable_se(struct nci_dev *ndev, u32 se_idx);
+int st_nci_se_io(struct nci_dev *ndev, u32 se_idx,
+ u8 *apdu, size_t apdu_length,
+ se_io_cb_t cb, void *cb_context);
+int st_nci_hci_load_session(struct nci_dev *ndev);
+void st_nci_hci_event_received(struct nci_dev *ndev, u8 pipe,
+ u8 event, struct sk_buff *skb);
+void st_nci_hci_cmd_received(struct nci_dev *ndev, u8 pipe, u8 cmd,
+ struct sk_buff *skb);
+
+int st_nci_vendor_cmds_init(struct nci_dev *ndev);
+
+#endif /* __LOCAL_ST_NCI_H_ */
diff --git a/drivers/nfc/st-nci/vendor_cmds.c b/drivers/nfc/st-nci/vendor_cmds.c
new file mode 100644
index 000000000..1a836c77c
--- /dev/null
+++ b/drivers/nfc/st-nci/vendor_cmds.c
@@ -0,0 +1,478 @@
+/*
+ * Proprietary commands extension for STMicroelectronics NFC NCI Chip
+ *
+ * Copyright (C) 2014-2015 STMicroelectronics SAS. 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 <net/genetlink.h>
+#include <linux/module.h>
+#include <linux/nfc.h>
+#include <linux/delay.h>
+#include <net/nfc/nci_core.h>
+
+#include "st-nci.h"
+
+#define ST_NCI_HCI_DM_GETDATA 0x10
+#define ST_NCI_HCI_DM_PUTDATA 0x11
+#define ST_NCI_HCI_DM_LOAD 0x12
+#define ST_NCI_HCI_DM_GETINFO 0x13
+#define ST_NCI_HCI_DM_FWUPD_START 0x14
+#define ST_NCI_HCI_DM_FWUPD_STOP 0x15
+#define ST_NCI_HCI_DM_UPDATE_AID 0x20
+#define ST_NCI_HCI_DM_RESET 0x3e
+
+#define ST_NCI_HCI_DM_FIELD_GENERATOR 0x32
+#define ST_NCI_HCI_DM_VDC_MEASUREMENT_VALUE 0x33
+#define ST_NCI_HCI_DM_VDC_VALUE_COMPARISON 0x34
+
+#define ST_NCI_FACTORY_MODE_ON 1
+#define ST_NCI_FACTORY_MODE_OFF 0
+
+#define ST_NCI_EVT_POST_DATA 0x02
+
+struct get_param_data {
+ u8 gate;
+ u8 data;
+} __packed;
+
+static int st_nci_factory_mode(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+ struct st_nci_info *info = nci_get_drvdata(ndev);
+
+ if (data_len != 1)
+ return -EINVAL;
+
+ pr_debug("factory mode: %x\n", ((u8 *)data)[0]);
+
+ switch (((u8 *)data)[0]) {
+ case ST_NCI_FACTORY_MODE_ON:
+ test_and_set_bit(ST_NCI_FACTORY_MODE, &info->flags);
+ break;
+ case ST_NCI_FACTORY_MODE_OFF:
+ clear_bit(ST_NCI_FACTORY_MODE, &info->flags);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int st_nci_hci_clear_all_pipes(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+
+ return nci_hci_clear_all_pipes(ndev);
+}
+
+static int st_nci_hci_dm_put_data(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+
+ return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
+ ST_NCI_HCI_DM_PUTDATA, data,
+ data_len, NULL);
+}
+
+static int st_nci_hci_dm_update_aid(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+
+ return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
+ ST_NCI_HCI_DM_UPDATE_AID, data, data_len, NULL);
+}
+
+static int st_nci_hci_dm_get_info(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ int r;
+ struct sk_buff *msg, *skb;
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+
+ r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, ST_NCI_HCI_DM_GETINFO,
+ data, data_len, &skb);
+ if (r)
+ goto exit;
+
+ msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI,
+ HCI_DM_GET_INFO, skb->len);
+ if (!msg) {
+ r = -ENOMEM;
+ goto free_skb;
+ }
+
+ if (nla_put(msg, NFC_ATTR_VENDOR_DATA, skb->len, skb->data)) {
+ kfree_skb(msg);
+ r = -ENOBUFS;
+ goto free_skb;
+ }
+
+ r = nfc_vendor_cmd_reply(msg);
+
+free_skb:
+ kfree_skb(skb);
+exit:
+ return r;
+}
+
+static int st_nci_hci_dm_get_data(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ int r;
+ struct sk_buff *msg, *skb;
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+
+ r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, ST_NCI_HCI_DM_GETDATA,
+ data, data_len, &skb);
+ if (r)
+ goto exit;
+
+ msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI,
+ HCI_DM_GET_DATA, skb->len);
+ if (!msg) {
+ r = -ENOMEM;
+ goto free_skb;
+ }
+
+ if (nla_put(msg, NFC_ATTR_VENDOR_DATA, skb->len, skb->data)) {
+ kfree_skb(msg);
+ r = -ENOBUFS;
+ goto free_skb;
+ }
+
+ r = nfc_vendor_cmd_reply(msg);
+
+free_skb:
+ kfree_skb(skb);
+exit:
+ return r;
+}
+
+static int st_nci_hci_dm_fwupd_start(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ int r;
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+
+ dev->fw_download_in_progress = true;
+ r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
+ ST_NCI_HCI_DM_FWUPD_START, data, data_len, NULL);
+ if (r)
+ dev->fw_download_in_progress = false;
+
+ return r;
+}
+
+static int st_nci_hci_dm_fwupd_end(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+
+ return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
+ ST_NCI_HCI_DM_FWUPD_STOP, data, data_len, NULL);
+}
+
+static int st_nci_hci_dm_direct_load(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+
+ if (dev->fw_download_in_progress) {
+ dev->fw_download_in_progress = false;
+ return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
+ ST_NCI_HCI_DM_LOAD, data, data_len, NULL);
+ }
+ return -EPROTO;
+}
+
+static int st_nci_hci_dm_reset(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+
+ nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
+ ST_NCI_HCI_DM_RESET, data, data_len, NULL);
+ msleep(200);
+
+ return 0;
+}
+
+static int st_nci_hci_get_param(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ int r;
+ struct sk_buff *msg, *skb;
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+ struct get_param_data *param = (struct get_param_data *)data;
+
+ if (data_len < sizeof(struct get_param_data))
+ return -EPROTO;
+
+ r = nci_hci_get_param(ndev, param->gate, param->data, &skb);
+ if (r)
+ goto exit;
+
+ msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI,
+ HCI_GET_PARAM, skb->len);
+ if (!msg) {
+ r = -ENOMEM;
+ goto free_skb;
+ }
+
+ if (nla_put(msg, NFC_ATTR_VENDOR_DATA, skb->len, skb->data)) {
+ kfree_skb(msg);
+ r = -ENOBUFS;
+ goto free_skb;
+ }
+
+ r = nfc_vendor_cmd_reply(msg);
+
+free_skb:
+ kfree_skb(skb);
+exit:
+ return r;
+}
+
+static int st_nci_hci_dm_field_generator(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+
+ return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
+ ST_NCI_HCI_DM_FIELD_GENERATOR, data, data_len, NULL);
+}
+
+static int st_nci_hci_dm_vdc_measurement_value(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ int r;
+ struct sk_buff *msg, *skb;
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+
+ if (data_len != 4)
+ return -EPROTO;
+
+ r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
+ ST_NCI_HCI_DM_VDC_MEASUREMENT_VALUE,
+ data, data_len, &skb);
+ if (r)
+ goto exit;
+
+ msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI,
+ HCI_DM_VDC_MEASUREMENT_VALUE, skb->len);
+ if (!msg) {
+ r = -ENOMEM;
+ goto free_skb;
+ }
+
+ if (nla_put(msg, NFC_ATTR_VENDOR_DATA, skb->len, skb->data)) {
+ kfree_skb(msg);
+ r = -ENOBUFS;
+ goto free_skb;
+ }
+
+ r = nfc_vendor_cmd_reply(msg);
+
+free_skb:
+ kfree_skb(skb);
+exit:
+ return r;
+}
+
+static int st_nci_hci_dm_vdc_value_comparison(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ int r;
+ struct sk_buff *msg, *skb;
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+
+ if (data_len != 2)
+ return -EPROTO;
+
+ r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
+ ST_NCI_HCI_DM_VDC_VALUE_COMPARISON,
+ data, data_len, &skb);
+ if (r)
+ goto exit;
+
+ msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI,
+ HCI_DM_VDC_VALUE_COMPARISON, skb->len);
+ if (!msg) {
+ r = -ENOMEM;
+ goto free_skb;
+ }
+
+ if (nla_put(msg, NFC_ATTR_VENDOR_DATA, skb->len, skb->data)) {
+ kfree_skb(msg);
+ r = -ENOBUFS;
+ goto free_skb;
+ }
+
+ r = nfc_vendor_cmd_reply(msg);
+
+free_skb:
+ kfree_skb(skb);
+exit:
+ return r;
+}
+
+static int st_nci_loopback(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ int r;
+ struct sk_buff *msg, *skb;
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+
+ if (data_len <= 0)
+ return -EPROTO;
+
+ r = nci_nfcc_loopback(ndev, data, data_len, &skb);
+ if (r < 0)
+ return r;
+
+ msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI,
+ LOOPBACK, skb->len);
+ if (!msg) {
+ r = -ENOMEM;
+ goto free_skb;
+ }
+
+ if (nla_put(msg, NFC_ATTR_VENDOR_DATA, skb->len, skb->data)) {
+ kfree_skb(msg);
+ r = -ENOBUFS;
+ goto free_skb;
+ }
+
+ r = nfc_vendor_cmd_reply(msg);
+free_skb:
+ kfree_skb(skb);
+ return r;
+}
+
+static int st_nci_manufacturer_specific(struct nfc_dev *dev, void *data,
+ size_t data_len)
+{
+ struct sk_buff *msg;
+ struct nci_dev *ndev = nfc_get_drvdata(dev);
+
+ msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI,
+ MANUFACTURER_SPECIFIC,
+ sizeof(ndev->manufact_specific_info));
+ if (!msg)
+ return -ENOMEM;
+
+ if (nla_put(msg, NFC_ATTR_VENDOR_DATA, sizeof(ndev->manufact_specific_info),
+ &ndev->manufact_specific_info)) {
+ kfree_skb(msg);
+ return -ENOBUFS;
+ }
+
+ return nfc_vendor_cmd_reply(msg);
+}
+
+static struct nfc_vendor_cmd st_nci_vendor_cmds[] = {
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = FACTORY_MODE,
+ .doit = st_nci_factory_mode,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = HCI_CLEAR_ALL_PIPES,
+ .doit = st_nci_hci_clear_all_pipes,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = HCI_DM_PUT_DATA,
+ .doit = st_nci_hci_dm_put_data,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = HCI_DM_UPDATE_AID,
+ .doit = st_nci_hci_dm_update_aid,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = HCI_DM_GET_INFO,
+ .doit = st_nci_hci_dm_get_info,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = HCI_DM_GET_DATA,
+ .doit = st_nci_hci_dm_get_data,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = HCI_DM_DIRECT_LOAD,
+ .doit = st_nci_hci_dm_direct_load,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = HCI_DM_RESET,
+ .doit = st_nci_hci_dm_reset,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = HCI_GET_PARAM,
+ .doit = st_nci_hci_get_param,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = HCI_DM_FIELD_GENERATOR,
+ .doit = st_nci_hci_dm_field_generator,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = HCI_DM_FWUPD_START,
+ .doit = st_nci_hci_dm_fwupd_start,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = HCI_DM_FWUPD_END,
+ .doit = st_nci_hci_dm_fwupd_end,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = LOOPBACK,
+ .doit = st_nci_loopback,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = HCI_DM_VDC_MEASUREMENT_VALUE,
+ .doit = st_nci_hci_dm_vdc_measurement_value,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = HCI_DM_VDC_VALUE_COMPARISON,
+ .doit = st_nci_hci_dm_vdc_value_comparison,
+ },
+ {
+ .vendor_id = ST_NCI_VENDOR_OUI,
+ .subcmd = MANUFACTURER_SPECIFIC,
+ .doit = st_nci_manufacturer_specific,
+ },
+};
+
+int st_nci_vendor_cmds_init(struct nci_dev *ndev)
+{
+ return nfc_set_vendor_cmds(ndev->nfc_dev, st_nci_vendor_cmds,
+ sizeof(st_nci_vendor_cmds));
+}
+EXPORT_SYMBOL(st_nci_vendor_cmds_init);