OSDN Git Service

NFC: Add STMicroelectronics ST95HF driver
authorShikha Singh <shikha.singh@st.com>
Mon, 21 Dec 2015 23:03:30 +0000 (00:03 +0100)
committerSamuel Ortiz <sameo@linux.intel.com>
Tue, 29 Dec 2015 18:06:11 +0000 (19:06 +0100)
This driver supports STMicroelectronics NFC Transceiver
"ST95HF", in in initiator role to read/write ISO14443 Type 4A,
ISO14443 Type 4B and ISO15693 Type5 tags.

The ST95HF datasheet is available here:
http://www.st.com/web/en/resource/technical/document/datasheet/DM00102056.pdf

Signed-off-by: Shikha Singh <shikha.singh@st.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
drivers/nfc/Kconfig
drivers/nfc/Makefile
drivers/nfc/st95hf/Kconfig [new file with mode: 0644]
drivers/nfc/st95hf/Makefile [new file with mode: 0644]
drivers/nfc/st95hf/core.c [new file with mode: 0644]
drivers/nfc/st95hf/spi.c [new file with mode: 0644]
drivers/nfc/st95hf/spi.h [new file with mode: 0644]

index 0d6003d..7437c9d 100644 (file)
@@ -76,4 +76,5 @@ source "drivers/nfc/st21nfca/Kconfig"
 source "drivers/nfc/st-nci/Kconfig"
 source "drivers/nfc/nxp-nci/Kconfig"
 source "drivers/nfc/s3fwrn5/Kconfig"
+source "drivers/nfc/st95hf/Kconfig"
 endmenu
index e362141..0a99e67 100644 (file)
@@ -16,3 +16,4 @@ obj-$(CONFIG_NFC_ST21NFCA)    += st21nfca/
 obj-$(CONFIG_NFC_ST_NCI)       += st-nci/
 obj-$(CONFIG_NFC_NXP_NCI)      += nxp-nci/
 obj-$(CONFIG_NFC_S3FWRN5)      += s3fwrn5/
+obj-$(CONFIG_NFC_ST95HF)       += st95hf/
diff --git a/drivers/nfc/st95hf/Kconfig b/drivers/nfc/st95hf/Kconfig
new file mode 100644 (file)
index 0000000..224f266
--- /dev/null
@@ -0,0 +1,10 @@
+config NFC_ST95HF
+       tristate "ST95HF NFC Transceiver driver"
+       depends on SPI && NFC_DIGITAL
+       help
+       This enables the ST NFC driver for ST95HF NFC transceiver.
+       This makes use of SPI framework to communicate with transceiver
+       and registered with NFC digital core to support Linux NFC framework.
+
+       Say Y here to compile support for ST NFC transceiver ST95HF
+       linux driver into the kernel or say M to compile it as module.
diff --git a/drivers/nfc/st95hf/Makefile b/drivers/nfc/st95hf/Makefile
new file mode 100644 (file)
index 0000000..00760b3
--- /dev/null
@@ -0,0 +1,6 @@
+#
+# Makefile for STMicroelectronics NFC transceiver ST95HF
+#
+
+obj-$(CONFIG_NFC_ST95HF)       += st95hf.o
+st95hf-objs                    := spi.o core.o
diff --git a/drivers/nfc/st95hf/core.c b/drivers/nfc/st95hf/core.c
new file mode 100644 (file)
index 0000000..454efc6
--- /dev/null
@@ -0,0 +1,1273 @@
+/*
+ * --------------------------------------------------------------------
+ * Driver for ST NFC Transceiver ST95HF
+ * --------------------------------------------------------------------
+ * Copyright (C) 2015 STMicroelectronics Pvt. Ltd. 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/err.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/nfc.h>
+#include <linux/of_gpio.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/wait.h>
+#include <net/nfc/digital.h>
+#include <net/nfc/nfc.h>
+
+#include "spi.h"
+
+/* supported protocols */
+#define ST95HF_SUPPORTED_PROT          (NFC_PROTO_ISO14443_MASK | \
+                                       NFC_PROTO_ISO14443_B_MASK | \
+                                       NFC_PROTO_ISO15693_MASK)
+/* driver capabilities */
+#define ST95HF_CAPABILITIES            NFC_DIGITAL_DRV_CAPS_IN_CRC
+
+/* Command Send Interface */
+/* ST95HF_COMMAND_SEND CMD Ids */
+#define ECHO_CMD                       0x55
+#define WRITE_REGISTER_CMD             0x9
+#define PROTOCOL_SELECT_CMD            0x2
+#define SEND_RECEIVE_CMD               0x4
+
+/* Select protocol codes */
+#define ISO15693_PROTOCOL_CODE         0x1
+#define ISO14443A_PROTOCOL_CODE                0x2
+#define ISO14443B_PROTOCOL_CODE                0x3
+
+/*
+ * head room len is 3
+ * 1 byte for control byte
+ * 1 byte for cmd
+ * 1 byte for size
+ */
+#define ST95HF_HEADROOM_LEN            3
+
+/*
+ * tailroom is 1 for ISO14443A
+ * and 0 for ISO14443B/ISO15693,
+ * hence the max value 1 should be
+ * taken.
+ */
+#define ST95HF_TAILROOM_LEN            1
+
+/* Command Response interface */
+#define MAX_RESPONSE_BUFFER_SIZE       280
+#define ECHORESPONSE                   0x55
+#define ST95HF_ERR_MASK                        0xF
+#define ST95HF_TIMEOUT_ERROR           0x87
+#define ST95HF_NFCA_CRC_ERR_MASK       0x20
+#define ST95HF_NFCB_CRC_ERR_MASK       0x01
+
+/* ST95HF transmission flag values */
+#define TRFLAG_NFCA_SHORT_FRAME                0x07
+#define TRFLAG_NFCA_STD_FRAME          0x08
+#define TRFLAG_NFCA_STD_FRAME_CRC      0x28
+
+/* Misc defs */
+#define HIGH                           1
+#define LOW                            0
+#define ISO14443A_RATS_REQ             0xE0
+#define RATS_TB1_PRESENT_MASK          0x20
+#define RATS_TA1_PRESENT_MASK          0x10
+#define TB1_FWI_MASK                   0xF0
+#define WTX_REQ_FROM_TAG               0xF2
+
+#define MAX_CMD_LEN                    0x7
+
+#define MAX_CMD_PARAMS                 4
+struct cmd {
+       int cmd_len;
+       unsigned char cmd_id;
+       unsigned char no_cmd_params;
+       unsigned char cmd_params[MAX_CMD_PARAMS];
+       enum req_type req;
+};
+
+struct param_list {
+       int param_offset;
+       int new_param_val;
+};
+
+/*
+ * List of top-level cmds to be used internally by the driver.
+ * All these commands are build on top of ST95HF basic commands
+ * such as SEND_RECEIVE_CMD, PROTOCOL_SELECT_CMD, etc.
+ * These top level cmds are used internally while implementing various ops of
+ * digital layer/driver probe or extending the digital framework layer for
+ * features that are not yet implemented there, for example, WTX cmd handling.
+ */
+enum st95hf_cmd_list {
+       CMD_ECHO,
+       CMD_ISO14443A_CONFIG,
+       CMD_ISO14443A_DEMOGAIN,
+       CMD_ISO14443B_DEMOGAIN,
+       CMD_ISO14443A_PROTOCOL_SELECT,
+       CMD_ISO14443B_PROTOCOL_SELECT,
+       CMD_WTX_RESPONSE,
+       CMD_FIELD_OFF,
+       CMD_ISO15693_PROTOCOL_SELECT,
+};
+
+static const struct cmd cmd_array[] = {
+       [CMD_ECHO] = {
+               .cmd_len = 0x2,
+               .cmd_id = ECHO_CMD,
+               .no_cmd_params = 0,
+               .req = SYNC,
+       },
+       [CMD_ISO14443A_CONFIG] = {
+               .cmd_len = 0x7,
+               .cmd_id = WRITE_REGISTER_CMD,
+               .no_cmd_params = 0x4,
+               .cmd_params = {0x3A, 0x00, 0x5A, 0x04},
+               .req = SYNC,
+       },
+       [CMD_ISO14443A_DEMOGAIN] = {
+               .cmd_len = 0x7,
+               .cmd_id = WRITE_REGISTER_CMD,
+               .no_cmd_params = 0x4,
+               .cmd_params = {0x68, 0x01, 0x01, 0xDF},
+               .req = SYNC,
+       },
+       [CMD_ISO14443B_DEMOGAIN] = {
+               .cmd_len = 0x7,
+               .cmd_id = WRITE_REGISTER_CMD,
+               .no_cmd_params = 0x4,
+               .cmd_params = {0x68, 0x01, 0x01, 0x51},
+               .req = SYNC,
+       },
+       [CMD_ISO14443A_PROTOCOL_SELECT] = {
+               .cmd_len = 0x7,
+               .cmd_id = PROTOCOL_SELECT_CMD,
+               .no_cmd_params = 0x4,
+               .cmd_params = {ISO14443A_PROTOCOL_CODE, 0x00, 0x01, 0xA0},
+               .req = SYNC,
+       },
+       [CMD_ISO14443B_PROTOCOL_SELECT] = {
+               .cmd_len = 0x7,
+               .cmd_id = PROTOCOL_SELECT_CMD,
+               .no_cmd_params = 0x4,
+               .cmd_params = {ISO14443B_PROTOCOL_CODE, 0x01, 0x03, 0xFF},
+               .req = SYNC,
+       },
+       [CMD_WTX_RESPONSE] = {
+               .cmd_len = 0x6,
+               .cmd_id = SEND_RECEIVE_CMD,
+               .no_cmd_params = 0x3,
+               .cmd_params = {0xF2, 0x00, TRFLAG_NFCA_STD_FRAME_CRC},
+               .req = ASYNC,
+       },
+       [CMD_FIELD_OFF] = {
+               .cmd_len = 0x5,
+               .cmd_id = PROTOCOL_SELECT_CMD,
+               .no_cmd_params = 0x2,
+               .cmd_params = {0x0, 0x0},
+               .req = SYNC,
+       },
+       [CMD_ISO15693_PROTOCOL_SELECT] = {
+               .cmd_len = 0x5,
+               .cmd_id = PROTOCOL_SELECT_CMD,
+               .no_cmd_params = 0x2,
+               .cmd_params = {ISO15693_PROTOCOL_CODE, 0x0D},
+               .req = SYNC,
+       },
+};
+
+/* st95_digital_cmd_complete_arg stores client context */
+struct st95_digital_cmd_complete_arg {
+       struct sk_buff *skb_resp;
+       nfc_digital_cmd_complete_t complete_cb;
+       void *cb_usrarg;
+       bool rats;
+};
+
+/*
+ * structure containing ST95HF driver specific data.
+ * @spicontext: structure containing information required
+ *     for spi communication between st95hf and host.
+ * @ddev: nfc digital device object.
+ * @nfcdev: nfc device object.
+ * @enable_gpio: gpio used to enable st95hf transceiver.
+ * @complete_cb_arg: structure to store various context information
+ *     that is passed from nfc requesting thread to the threaded ISR.
+ * @st95hf_supply: regulator "consumer" for NFC device.
+ * @sendrcv_trflag: last byte of frame send by sendrecv command
+ *     of st95hf. This byte contains transmission flag info.
+ * @exchange_lock: semaphore used for signaling the st95hf_remove
+ *     function that the last outstanding async nfc request is finished.
+ * @rm_lock: mutex for ensuring safe access of nfc digital object
+ *     from threaded ISR. Usage of this mutex avoids any race between
+ *     deletion of the object from st95hf_remove() and its access from
+ *     the threaded ISR.
+ * @nfcdev_free: flag to have the state of nfc device object.
+ *     [alive | died]
+ * @current_protocol: current nfc protocol.
+ * @current_rf_tech: current rf technology.
+ * @fwi: frame waiting index, received in reply of RATS according to
+ *     digital protocol.
+ */
+struct st95hf_context {
+       struct st95hf_spi_context spicontext;
+       struct nfc_digital_dev *ddev;
+       struct nfc_dev *nfcdev;
+       unsigned int enable_gpio;
+       struct st95_digital_cmd_complete_arg complete_cb_arg;
+       struct regulator *st95hf_supply;
+       unsigned char sendrcv_trflag;
+       struct semaphore exchange_lock;
+       struct mutex rm_lock;
+       bool nfcdev_free;
+       u8 current_protocol;
+       u8 current_rf_tech;
+       int fwi;
+};
+
+/*
+ * st95hf_send_recv_cmd() is for sending commands to ST95HF
+ * that are described in the cmd_array[]. It can optionally
+ * receive the response if the cmd request is of type
+ * SYNC. For that to happen caller must pass true to recv_res.
+ * For ASYNC request, recv_res is ignored and the
+ * function will never try to receive the response on behalf
+ * of the caller.
+ */
+static int st95hf_send_recv_cmd(struct st95hf_context *st95context,
+                               enum st95hf_cmd_list cmd,
+                               int no_modif,
+                               struct param_list *list_array,
+                               bool recv_res)
+{
+       unsigned char spi_cmd_buffer[MAX_CMD_LEN];
+       int i, ret;
+       struct device *dev = &st95context->spicontext.spidev->dev;
+
+       if (cmd_array[cmd].cmd_len > MAX_CMD_LEN)
+               return -EINVAL;
+       if (cmd_array[cmd].no_cmd_params < no_modif)
+               return -EINVAL;
+       if (no_modif && !list_array)
+               return -EINVAL;
+
+       spi_cmd_buffer[0] = ST95HF_COMMAND_SEND;
+       spi_cmd_buffer[1] = cmd_array[cmd].cmd_id;
+       spi_cmd_buffer[2] = cmd_array[cmd].no_cmd_params;
+
+       memcpy(&spi_cmd_buffer[3], cmd_array[cmd].cmd_params,
+              spi_cmd_buffer[2]);
+
+       for (i = 0; i < no_modif; i++) {
+               if (list_array[i].param_offset >= cmd_array[cmd].no_cmd_params)
+                       return -EINVAL;
+               spi_cmd_buffer[3 + list_array[i].param_offset] =
+                                               list_array[i].new_param_val;
+       }
+
+       ret = st95hf_spi_send(&st95context->spicontext,
+                             spi_cmd_buffer,
+                             cmd_array[cmd].cmd_len,
+                             cmd_array[cmd].req);
+       if (ret) {
+               dev_err(dev, "st95hf_spi_send failed with error %d\n", ret);
+               return ret;
+       }
+
+       if (cmd_array[cmd].req == SYNC && recv_res) {
+               unsigned char st95hf_response_arr[2];
+
+               ret = st95hf_spi_recv_response(&st95context->spicontext,
+                                              st95hf_response_arr);
+               if (ret < 0) {
+                       dev_err(dev, "spi error from st95hf_spi_recv_response(), err = 0x%x\n",
+                               ret);
+                       return ret;
+               }
+
+               if (st95hf_response_arr[0]) {
+                       dev_err(dev, "st95hf error from st95hf_spi_recv_response(), err = 0x%x\n",
+                               st95hf_response_arr[0]);
+                       return -EIO;
+               }
+       }
+
+       return 0;
+}
+
+static int st95hf_echo_command(struct st95hf_context *st95context)
+{
+       int result = 0;
+       unsigned char echo_response;
+
+       result = st95hf_send_recv_cmd(st95context, CMD_ECHO, 0, NULL, false);
+       if (result)
+               return result;
+
+       /* If control reached here, response can be taken */
+       result = st95hf_spi_recv_echo_res(&st95context->spicontext,
+                                         &echo_response);
+       if (result) {
+               dev_err(&st95context->spicontext.spidev->dev,
+                       "err: echo response receieve error = 0x%x\n", result);
+               return result;
+       }
+
+       if (echo_response == ECHORESPONSE)
+               return 0;
+
+       dev_err(&st95context->spicontext.spidev->dev, "err: echo res is 0x%x\n",
+               echo_response);
+
+       return -EIO;
+}
+
+static int secondary_configuration_type4a(struct st95hf_context *stcontext)
+{
+       int result = 0;
+       struct device *dev = &stcontext->nfcdev->dev;
+
+       /* 14443A config setting after select protocol */
+       result = st95hf_send_recv_cmd(stcontext,
+                                     CMD_ISO14443A_CONFIG,
+                                     0,
+                                     NULL,
+                                     true);
+       if (result) {
+               dev_err(dev, "type a config cmd, err = 0x%x\n", result);
+               return result;
+       }
+
+       /* 14443A demo gain setting */
+       result = st95hf_send_recv_cmd(stcontext,
+                                     CMD_ISO14443A_DEMOGAIN,
+                                     0,
+                                     NULL,
+                                     true);
+       if (result)
+               dev_err(dev, "type a demogain cmd, err = 0x%x\n", result);
+
+       return result;
+}
+
+static int secondary_configuration_type4b(struct st95hf_context *stcontext)
+{
+       int result = 0;
+       struct device *dev = &stcontext->nfcdev->dev;
+
+       result = st95hf_send_recv_cmd(stcontext,
+                                     CMD_ISO14443B_DEMOGAIN,
+                                     0,
+                                     NULL,
+                                     true);
+       if (result)
+               dev_err(dev, "type b demogain cmd, err = 0x%x\n", result);
+
+       return result;
+}
+
+static int st95hf_select_protocol(struct st95hf_context *stcontext, int type)
+{
+       int result = 0;
+       struct device *dev;
+
+       dev = &stcontext->nfcdev->dev;
+
+       switch (type) {
+       case NFC_DIGITAL_RF_TECH_106A:
+               stcontext->current_rf_tech = NFC_DIGITAL_RF_TECH_106A;
+               result = st95hf_send_recv_cmd(stcontext,
+                                             CMD_ISO14443A_PROTOCOL_SELECT,
+                                             0,
+                                             NULL,
+                                             true);
+               if (result) {
+                       dev_err(dev, "protocol sel, err = 0x%x\n",
+                               result);
+                       return result;
+               }
+
+               /* secondary config. for 14443Type 4A after protocol select */
+               result = secondary_configuration_type4a(stcontext);
+               if (result) {
+                       dev_err(dev, "type a secondary config, err = 0x%x\n",
+                               result);
+                       return result;
+               }
+               break;
+       case NFC_DIGITAL_RF_TECH_106B:
+               stcontext->current_rf_tech = NFC_DIGITAL_RF_TECH_106B;
+               result = st95hf_send_recv_cmd(stcontext,
+                                             CMD_ISO14443B_PROTOCOL_SELECT,
+                                             0,
+                                             NULL,
+                                             true);
+               if (result) {
+                       dev_err(dev, "protocol sel send, err = 0x%x\n",
+                               result);
+                       return result;
+               }
+
+               /*
+                * delay of 5-6 ms is required after select protocol
+                * command in case of ISO14443 Type B
+                */
+               usleep_range(50000, 60000);
+
+               /* secondary config. for 14443Type 4B after protocol select */
+               result = secondary_configuration_type4b(stcontext);
+               if (result) {
+                       dev_err(dev, "type b secondary config, err = 0x%x\n",
+                               result);
+                       return result;
+               }
+               break;
+       case NFC_DIGITAL_RF_TECH_ISO15693:
+               stcontext->current_rf_tech = NFC_DIGITAL_RF_TECH_ISO15693;
+               result = st95hf_send_recv_cmd(stcontext,
+                                             CMD_ISO15693_PROTOCOL_SELECT,
+                                             0,
+                                             NULL,
+                                             true);
+               if (result) {
+                       dev_err(dev, "protocol sel send, err = 0x%x\n",
+                               result);
+                       return result;
+               }
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void st95hf_send_st95enable_negativepulse(struct st95hf_context *st95con)
+{
+       /* First make irq_in pin high */
+       gpio_set_value(st95con->enable_gpio, HIGH);
+
+       /* wait for 1 milisecond */
+       usleep_range(1000, 2000);
+
+       /* Make irq_in pin low */
+       gpio_set_value(st95con->enable_gpio, LOW);
+
+       /* wait for minimum interrupt pulse to make st95 active */
+       usleep_range(1000, 2000);
+
+       /* At end make it high */
+       gpio_set_value(st95con->enable_gpio, HIGH);
+}
+
+/*
+ * Send a reset sequence over SPI bus (Reset command + wait 3ms +
+ * negative pulse on st95hf enable gpio
+ */
+static int st95hf_send_spi_reset_sequence(struct st95hf_context *st95context)
+{
+       int result = 0;
+       unsigned char reset_cmd = ST95HF_COMMAND_RESET;
+
+       result = st95hf_spi_send(&st95context->spicontext,
+                                &reset_cmd,
+                                ST95HF_RESET_CMD_LEN,
+                                ASYNC);
+       if (result) {
+               dev_err(&st95context->spicontext.spidev->dev,
+                       "spi reset sequence cmd error = %d", result);
+               return result;
+       }
+
+       /* wait for 3 milisecond to complete the controller reset process */
+       usleep_range(3000, 4000);
+
+       /* send negative pulse to make st95hf active */
+       st95hf_send_st95enable_negativepulse(st95context);
+
+       /* wait for 10 milisecond : HFO setup time */
+       usleep_range(10000, 20000);
+
+       return result;
+}
+
+static int st95hf_por_sequence(struct st95hf_context *st95context)
+{
+       int nth_attempt = 1;
+       int result;
+
+       st95hf_send_st95enable_negativepulse(st95context);
+
+       usleep_range(5000, 6000);
+       do {
+               /* send an ECHO command and checks ST95HF response */
+               result = st95hf_echo_command(st95context);
+
+               dev_dbg(&st95context->spicontext.spidev->dev,
+                       "response from echo function = 0x%x, attempt = %d\n",
+                       result, nth_attempt);
+
+               if (!result)
+                       return 0;
+
+               /* send an pulse on IRQ in case of the chip is on sleep state */
+               if (nth_attempt == 2)
+                       st95hf_send_st95enable_negativepulse(st95context);
+               else
+                       st95hf_send_spi_reset_sequence(st95context);
+
+               /* delay of 50 milisecond */
+               usleep_range(50000, 51000);
+       } while (nth_attempt++ < 3);
+
+       return -ETIMEDOUT;
+}
+
+static int iso14443_config_fdt(struct st95hf_context *st95context, int wtxm)
+{
+       int result = 0;
+       struct device *dev = &st95context->spicontext.spidev->dev;
+       struct nfc_digital_dev *nfcddev = st95context->ddev;
+       unsigned char pp_typeb;
+       struct param_list new_params[2];
+
+       pp_typeb = cmd_array[CMD_ISO14443B_PROTOCOL_SELECT].cmd_params[2];
+
+       if (nfcddev->curr_protocol == NFC_PROTO_ISO14443 &&
+           st95context->fwi < 4)
+               st95context->fwi = 4;
+
+       new_params[0].param_offset = 2;
+       if (nfcddev->curr_protocol == NFC_PROTO_ISO14443)
+               new_params[0].new_param_val = st95context->fwi;
+       else if (nfcddev->curr_protocol == NFC_PROTO_ISO14443_B)
+               new_params[0].new_param_val = pp_typeb;
+
+       new_params[1].param_offset = 3;
+       new_params[1].new_param_val = wtxm;
+
+       switch (nfcddev->curr_protocol) {
+       case NFC_PROTO_ISO14443:
+               result = st95hf_send_recv_cmd(st95context,
+                                             CMD_ISO14443A_PROTOCOL_SELECT,
+                                             2,
+                                             new_params,
+                                             true);
+               if (result) {
+                       dev_err(dev, "WTX type a sel proto, err = 0x%x\n",
+                               result);
+                       return result;
+               }
+
+               /* secondary config. for 14443Type 4A after protocol select */
+               result = secondary_configuration_type4a(st95context);
+               if (result) {
+                       dev_err(dev, "WTX type a second. config, err = 0x%x\n",
+                               result);
+                       return result;
+               }
+               break;
+       case NFC_PROTO_ISO14443_B:
+               result = st95hf_send_recv_cmd(st95context,
+                                             CMD_ISO14443B_PROTOCOL_SELECT,
+                                             2,
+                                             new_params,
+                                             true);
+               if (result) {
+                       dev_err(dev, "WTX type b sel proto, err = 0x%x\n",
+                               result);
+                       return result;
+               }
+
+               /* secondary config. for 14443Type 4B after protocol select */
+               result = secondary_configuration_type4b(st95context);
+               if (result) {
+                       dev_err(dev, "WTX type b second. config, err = 0x%x\n",
+                               result);
+                       return result;
+               }
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int st95hf_handle_wtx(struct st95hf_context *stcontext,
+                            bool new_wtx,
+                            int wtx_val)
+{
+       int result = 0;
+       unsigned char val_mm = 0;
+       struct param_list new_params[1];
+       struct nfc_digital_dev *nfcddev = stcontext->ddev;
+       struct device *dev = &stcontext->nfcdev->dev;
+
+       if (new_wtx) {
+               result = iso14443_config_fdt(stcontext, wtx_val & 0x3f);
+               if (result) {
+                       dev_err(dev, "Config. setting error on WTX req, err = 0x%x\n",
+                               result);
+                       return result;
+               }
+
+               /* Send response of wtx with ASYNC as no response expected */
+               new_params[0].param_offset = 1;
+               new_params[0].new_param_val = wtx_val;
+
+               result = st95hf_send_recv_cmd(stcontext,
+                                             CMD_WTX_RESPONSE,
+                                             1,
+                                             new_params,
+                                             false);
+               if (result)
+                       dev_err(dev, "WTX response send, err = 0x%x\n", result);
+               return result;
+       }
+
+       /* if no new wtx, cofigure with default values */
+       if (nfcddev->curr_protocol == NFC_PROTO_ISO14443)
+               val_mm = cmd_array[CMD_ISO14443A_PROTOCOL_SELECT].cmd_params[3];
+       else if (nfcddev->curr_protocol == NFC_PROTO_ISO14443_B)
+               val_mm = cmd_array[CMD_ISO14443B_PROTOCOL_SELECT].cmd_params[3];
+
+       result = iso14443_config_fdt(stcontext, val_mm);
+       if (result)
+               dev_err(dev, "Default config. setting error after WTX processing, err = 0x%x\n",
+                       result);
+
+       return result;
+}
+
+static int st95hf_error_handling(struct st95hf_context *stcontext,
+                                struct sk_buff *skb_resp,
+                                int res_len)
+{
+       int result = 0;
+       unsigned char error_byte;
+       struct device *dev = &stcontext->nfcdev->dev;
+
+       /* First check ST95HF specific error */
+       if (skb_resp->data[0] & ST95HF_ERR_MASK) {
+               if (skb_resp->data[0] == ST95HF_TIMEOUT_ERROR)
+                       result = -ETIMEDOUT;
+               else
+                       result = -EIO;
+       return  result;
+       }
+
+       /* Check for CRC err only if CRC is present in the tag response */
+       switch (stcontext->current_rf_tech) {
+       case NFC_DIGITAL_RF_TECH_106A:
+               if (stcontext->sendrcv_trflag == TRFLAG_NFCA_STD_FRAME_CRC) {
+                       error_byte = skb_resp->data[res_len - 3];
+                       if (error_byte & ST95HF_NFCA_CRC_ERR_MASK) {
+                               /* CRC error occurred */
+                               dev_err(dev, "CRC error, byte received = 0x%x\n",
+                                       error_byte);
+                               result = -EIO;
+                       }
+               }
+               break;
+       case NFC_DIGITAL_RF_TECH_106B:
+       case NFC_DIGITAL_RF_TECH_ISO15693:
+               error_byte = skb_resp->data[res_len - 1];
+               if (error_byte & ST95HF_NFCB_CRC_ERR_MASK) {
+                       /* CRC error occurred */
+                       dev_err(dev, "CRC error, byte received = 0x%x\n",
+                               error_byte);
+                       result = -EIO;
+               }
+               break;
+       }
+
+       return result;
+}
+
+static int st95hf_response_handler(struct st95hf_context *stcontext,
+                                  struct sk_buff *skb_resp,
+                                  int res_len)
+{
+       int result = 0;
+       int skb_len;
+       unsigned char val_mm;
+       struct nfc_digital_dev *nfcddev = stcontext->ddev;
+       struct device *dev = &stcontext->nfcdev->dev;
+       struct st95_digital_cmd_complete_arg *cb_arg;
+
+       cb_arg = &stcontext->complete_cb_arg;
+
+       /* Process the response */
+       skb_put(skb_resp, res_len);
+
+       /* Remove st95 header */
+       skb_pull(skb_resp, 2);
+
+       skb_len = skb_resp->len;
+
+       /* check if it is case of RATS request reply & FWI is present */
+       if (nfcddev->curr_protocol == NFC_PROTO_ISO14443 && cb_arg->rats &&
+           (skb_resp->data[1] & RATS_TB1_PRESENT_MASK)) {
+               if (skb_resp->data[1] & RATS_TA1_PRESENT_MASK)
+                       stcontext->fwi =
+                               (skb_resp->data[3] & TB1_FWI_MASK) >> 4;
+               else
+                       stcontext->fwi =
+                               (skb_resp->data[2] & TB1_FWI_MASK) >> 4;
+
+               val_mm = cmd_array[CMD_ISO14443A_PROTOCOL_SELECT].cmd_params[3];
+
+               result = iso14443_config_fdt(stcontext, val_mm);
+               if (result) {
+                       dev_err(dev, "error in config_fdt to handle fwi of ATS, error=%d\n",
+                               result);
+                       return result;
+               }
+       }
+       cb_arg->rats = false;
+
+       /* Remove CRC bytes only if received frames data has an eod (CRC) */
+       switch (stcontext->current_rf_tech) {
+       case NFC_DIGITAL_RF_TECH_106A:
+               if (stcontext->sendrcv_trflag == TRFLAG_NFCA_STD_FRAME_CRC)
+                       skb_trim(skb_resp, (skb_len - 5));
+               else
+                       skb_trim(skb_resp, (skb_len - 3));
+               break;
+       case NFC_DIGITAL_RF_TECH_106B:
+       case NFC_DIGITAL_RF_TECH_ISO15693:
+               skb_trim(skb_resp, (skb_len - 3));
+               break;
+       }
+
+       return result;
+}
+
+static irqreturn_t irq_handler(int irq, void  *st95hfcontext)
+{
+       struct st95hf_context *stcontext  =
+               (struct st95hf_context *)st95hfcontext;
+
+       if (stcontext->spicontext.req_issync) {
+               complete(&stcontext->spicontext.done);
+               stcontext->spicontext.req_issync = false;
+               return IRQ_HANDLED;
+       }
+
+       return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t irq_thread_handler(int irq, void  *st95hfcontext)
+{
+       int result = 0;
+       int res_len;
+       static bool wtx;
+       struct device *dev;
+       struct device *spidevice;
+       struct nfc_digital_dev *nfcddev;
+       struct sk_buff *skb_resp;
+       struct st95hf_context *stcontext  =
+               (struct st95hf_context *)st95hfcontext;
+       struct st95_digital_cmd_complete_arg *cb_arg;
+
+       spidevice = &stcontext->spicontext.spidev->dev;
+
+       /*
+        * check semaphore, if not down() already, then we don't
+        * know in which context the ISR is called and surely it
+        * will be a bug. Note that down() of the semaphore is done
+        * in the corresponding st95hf_in_send_cmd() and then
+        * only this ISR should be called. ISR will up() the
+        * semaphore before leaving. Hence when the ISR is called
+        * the correct behaviour is down_trylock() should always
+        * return 1 (indicating semaphore cant be taken and hence no
+        * change in semaphore count).
+        * If not, then we up() the semaphore and crash on
+        * a BUG() !
+        */
+       if (!down_trylock(&stcontext->exchange_lock)) {
+               up(&stcontext->exchange_lock);
+               WARN(1, "unknown context in ST95HF ISR");
+               return IRQ_NONE;
+       }
+
+       cb_arg = &stcontext->complete_cb_arg;
+       skb_resp = cb_arg->skb_resp;
+
+       mutex_lock(&stcontext->rm_lock);
+       res_len = st95hf_spi_recv_response(&stcontext->spicontext,
+                                          skb_resp->data);
+       if (res_len < 0) {
+               dev_err(spidevice, "TISR spi response err = 0x%x\n", res_len);
+               result = res_len;
+               goto end;
+       }
+
+       /* if stcontext->nfcdev_free is true, it means remove already ran */
+       if (stcontext->nfcdev_free) {
+               result = -ENODEV;
+               goto end;
+       }
+
+       dev = &stcontext->nfcdev->dev;
+       nfcddev = stcontext->ddev;
+       if (skb_resp->data[2] == WTX_REQ_FROM_TAG) {
+               /* Request for new FWT from tag */
+               result = st95hf_handle_wtx(stcontext, true, skb_resp->data[3]);
+               if (result)
+                       goto end;
+
+               wtx = true;
+               mutex_unlock(&stcontext->rm_lock);
+               return IRQ_HANDLED;
+       }
+
+       result = st95hf_error_handling(stcontext, skb_resp, res_len);
+       if (result)
+               goto end;
+
+       result = st95hf_response_handler(stcontext, skb_resp, res_len);
+       if (result)
+               goto end;
+
+       /*
+        * If select protocol is done on wtx req. do select protocol
+        * again with default values
+        */
+       if (wtx) {
+               wtx = false;
+               result = st95hf_handle_wtx(stcontext, false, 0);
+               if (result)
+                       goto end;
+       }
+
+       /* call digital layer callback */
+       cb_arg->complete_cb(stcontext->ddev, cb_arg->cb_usrarg, skb_resp);
+
+       /* up the semaphore before returning */
+       up(&stcontext->exchange_lock);
+       mutex_unlock(&stcontext->rm_lock);
+
+       return IRQ_HANDLED;
+
+end:
+       kfree_skb(skb_resp);
+       wtx = false;
+       cb_arg->rats = false;
+       skb_resp = ERR_PTR(result);
+       /* call of callback with error */
+       cb_arg->complete_cb(stcontext->ddev, cb_arg->cb_usrarg, skb_resp);
+       /* up the semaphore before returning */
+       up(&stcontext->exchange_lock);
+       mutex_unlock(&stcontext->rm_lock);
+       return IRQ_HANDLED;
+}
+
+/* NFC ops functions definition */
+static int st95hf_in_configure_hw(struct nfc_digital_dev *ddev,
+                                 int type,
+                                 int param)
+{
+       struct st95hf_context *stcontext = nfc_digital_get_drvdata(ddev);
+
+       if (type == NFC_DIGITAL_CONFIG_RF_TECH)
+               return st95hf_select_protocol(stcontext, param);
+
+       if (type == NFC_DIGITAL_CONFIG_FRAMING) {
+               switch (param) {
+               case NFC_DIGITAL_FRAMING_NFCA_SHORT:
+                       stcontext->sendrcv_trflag = TRFLAG_NFCA_SHORT_FRAME;
+                       break;
+               case NFC_DIGITAL_FRAMING_NFCA_STANDARD:
+                       stcontext->sendrcv_trflag = TRFLAG_NFCA_STD_FRAME;
+                       break;
+               case NFC_DIGITAL_FRAMING_NFCA_T4T:
+               case NFC_DIGITAL_FRAMING_NFCA_NFC_DEP:
+               case NFC_DIGITAL_FRAMING_NFCA_STANDARD_WITH_CRC_A:
+                       stcontext->sendrcv_trflag = TRFLAG_NFCA_STD_FRAME_CRC;
+                       break;
+               case NFC_DIGITAL_FRAMING_NFCB:
+               case NFC_DIGITAL_FRAMING_ISO15693_INVENTORY:
+               case NFC_DIGITAL_FRAMING_ISO15693_T5T:
+                       break;
+               }
+       }
+
+       return 0;
+}
+
+static int rf_off(struct st95hf_context *stcontext)
+{
+       int rc;
+       struct device *dev;
+
+       dev = &stcontext->nfcdev->dev;
+
+       rc = st95hf_send_recv_cmd(stcontext, CMD_FIELD_OFF, 0, NULL, true);
+       if (rc)
+               dev_err(dev, "protocol sel send field off, err = 0x%x\n", rc);
+
+       return rc;
+}
+
+static int st95hf_in_send_cmd(struct nfc_digital_dev *ddev,
+                             struct sk_buff *skb,
+                             u16 timeout,
+                             nfc_digital_cmd_complete_t cb,
+                             void *arg)
+{
+       struct st95hf_context *stcontext = nfc_digital_get_drvdata(ddev);
+       int rc;
+       struct sk_buff *skb_resp;
+       int len_data_to_tag = 0;
+
+       skb_resp = nfc_alloc_recv_skb(MAX_RESPONSE_BUFFER_SIZE, GFP_KERNEL);
+       if (!skb_resp) {
+               rc = -ENOMEM;
+               goto error;
+       }
+
+       switch (stcontext->current_rf_tech) {
+       case NFC_DIGITAL_RF_TECH_106A:
+               len_data_to_tag = skb->len + 1;
+               *skb_put(skb, 1) = stcontext->sendrcv_trflag;
+               break;
+       case NFC_DIGITAL_RF_TECH_106B:
+       case NFC_DIGITAL_RF_TECH_ISO15693:
+               len_data_to_tag = skb->len;
+               break;
+       default:
+               rc = -EINVAL;
+               goto free_skb_resp;
+       }
+
+       skb_push(skb, 3);
+       skb->data[0] = ST95HF_COMMAND_SEND;
+       skb->data[1] = SEND_RECEIVE_CMD;
+       skb->data[2] = len_data_to_tag;
+
+       stcontext->complete_cb_arg.skb_resp = skb_resp;
+       stcontext->complete_cb_arg.cb_usrarg = arg;
+       stcontext->complete_cb_arg.complete_cb = cb;
+
+       if ((skb->data[3] == ISO14443A_RATS_REQ) &&
+           ddev->curr_protocol == NFC_PROTO_ISO14443)
+               stcontext->complete_cb_arg.rats = true;
+
+       /*
+        * down the semaphore to indicate to remove func that an
+        * ISR is pending, note that it will not block here in any case.
+        * If found blocked, it is a BUG!
+        */
+       rc = down_killable(&stcontext->exchange_lock);
+       if (rc) {
+               WARN(1, "Semaphore is not found up in st95hf_in_send_cmd\n");
+               return rc;
+       }
+
+       rc = st95hf_spi_send(&stcontext->spicontext, skb->data,
+                            skb->len,
+                            ASYNC);
+       if (rc) {
+               dev_err(&stcontext->nfcdev->dev,
+                       "Error %d trying to perform data_exchange", rc);
+               /* up the semaphore since ISR will never come in this case */
+               up(&stcontext->exchange_lock);
+               goto free_skb_resp;
+       }
+
+       kfree_skb(skb);
+
+       return rc;
+
+free_skb_resp:
+       kfree_skb(skb_resp);
+error:
+       return rc;
+}
+
+/* p2p will be supported in a later release ! */
+static int st95hf_tg_configure_hw(struct nfc_digital_dev *ddev,
+                                 int type,
+                                 int param)
+{
+       return 0;
+}
+
+static int st95hf_tg_send_cmd(struct nfc_digital_dev *ddev,
+                             struct sk_buff *skb,
+                             u16 timeout,
+                             nfc_digital_cmd_complete_t cb,
+                             void *arg)
+{
+       return 0;
+}
+
+static int st95hf_tg_listen(struct nfc_digital_dev *ddev,
+                           u16 timeout,
+                           nfc_digital_cmd_complete_t cb,
+                           void *arg)
+{
+       return 0;
+}
+
+static int st95hf_tg_get_rf_tech(struct nfc_digital_dev *ddev, u8 *rf_tech)
+{
+       return 0;
+}
+
+static int st95hf_switch_rf(struct nfc_digital_dev *ddev, bool on)
+{
+       u8 rf_tech;
+       struct st95hf_context *stcontext = nfc_digital_get_drvdata(ddev);
+
+       rf_tech = ddev->curr_rf_tech;
+
+       if (on)
+               /* switch on RF field */
+               return st95hf_select_protocol(stcontext, rf_tech);
+
+       /* switch OFF RF field */
+       return rf_off(stcontext);
+}
+
+/* TODO st95hf_abort_cmd */
+static void st95hf_abort_cmd(struct nfc_digital_dev *ddev)
+{
+}
+
+static struct nfc_digital_ops st95hf_nfc_digital_ops = {
+       .in_configure_hw = st95hf_in_configure_hw,
+       .in_send_cmd = st95hf_in_send_cmd,
+
+       .tg_listen = st95hf_tg_listen,
+       .tg_configure_hw = st95hf_tg_configure_hw,
+       .tg_send_cmd = st95hf_tg_send_cmd,
+       .tg_get_rf_tech = st95hf_tg_get_rf_tech,
+
+       .switch_rf = st95hf_switch_rf,
+       .abort_cmd = st95hf_abort_cmd,
+};
+
+static const struct spi_device_id st95hf_id[] = {
+       { "st95hf", 0 },
+       {}
+};
+MODULE_DEVICE_TABLE(spi, st95hf_id);
+
+static int st95hf_probe(struct spi_device *nfc_spi_dev)
+{
+       int ret;
+
+       struct st95hf_context *st95context;
+       struct st95hf_spi_context *spicontext;
+
+       nfc_info(&nfc_spi_dev->dev, "ST95HF driver probe called.\n");
+
+       st95context = devm_kzalloc(&nfc_spi_dev->dev,
+                                  sizeof(struct st95hf_context),
+                                  GFP_KERNEL);
+       if (!st95context)
+               return -ENOMEM;
+
+       spicontext = &st95context->spicontext;
+
+       spicontext->spidev = nfc_spi_dev;
+
+       st95context->fwi =
+               cmd_array[CMD_ISO14443A_PROTOCOL_SELECT].cmd_params[2];
+
+       if (device_property_present(&nfc_spi_dev->dev, "st95hfvin")) {
+               st95context->st95hf_supply =
+                       devm_regulator_get(&nfc_spi_dev->dev,
+                                          "st95hfvin");
+               if (IS_ERR(st95context->st95hf_supply)) {
+                       dev_err(&nfc_spi_dev->dev, "failed to acquire regulator\n");
+                       return PTR_ERR(st95context->st95hf_supply);
+               }
+
+               ret = regulator_enable(st95context->st95hf_supply);
+               if (ret) {
+                       dev_err(&nfc_spi_dev->dev, "failed to enable regulator\n");
+                       return ret;
+               }
+       }
+
+       init_completion(&spicontext->done);
+       mutex_init(&spicontext->spi_lock);
+
+       /*
+        * Store spicontext in spi device object for using it in
+        * remove function
+        */
+       dev_set_drvdata(&nfc_spi_dev->dev, spicontext);
+
+       st95context->enable_gpio =
+               of_get_named_gpio(nfc_spi_dev->dev.of_node,
+                                 "enable-gpio",
+                                 0);
+       if (!gpio_is_valid(st95context->enable_gpio)) {
+               dev_err(&nfc_spi_dev->dev, "No valid enable gpio\n");
+               ret = st95context->enable_gpio;
+               goto err_disable_regulator;
+       }
+
+       ret = devm_gpio_request_one(&nfc_spi_dev->dev, st95context->enable_gpio,
+                                   GPIOF_DIR_OUT | GPIOF_INIT_HIGH,
+                                   "enable_gpio");
+       if (ret)
+               goto err_disable_regulator;
+
+       if (nfc_spi_dev->irq > 0) {
+               if (devm_request_threaded_irq(&nfc_spi_dev->dev,
+                                             nfc_spi_dev->irq,
+                                             irq_handler,
+                                             irq_thread_handler,
+                                             IRQF_TRIGGER_FALLING,
+                                             "st95hf",
+                                             (void *)st95context) < 0) {
+                       dev_err(&nfc_spi_dev->dev, "err: irq request for st95hf is failed\n");
+                       ret =  -EINVAL;
+                       goto err_disable_regulator;
+               }
+       } else {
+               dev_err(&nfc_spi_dev->dev, "not a valid IRQ associated with ST95HF\n");
+               ret = -EINVAL;
+               goto err_disable_regulator;
+       }
+
+       /*
+        * First reset SPI to handle warm reset of the system.
+        * It will put the ST95HF device in Power ON state
+        * which make the state of device identical to state
+        * at the time of cold reset of the system.
+        */
+       ret = st95hf_send_spi_reset_sequence(st95context);
+       if (ret) {
+               dev_err(&nfc_spi_dev->dev, "err: spi_reset_sequence failed\n");
+               goto err_disable_regulator;
+       }
+
+       /* call PowerOnReset sequence of ST95hf to activate it */
+       ret = st95hf_por_sequence(st95context);
+       if (ret) {
+               dev_err(&nfc_spi_dev->dev, "err: por seq failed for st95hf\n");
+               goto err_disable_regulator;
+       }
+
+       /* create NFC dev object and register with NFC Subsystem */
+       st95context->ddev = nfc_digital_allocate_device(&st95hf_nfc_digital_ops,
+                                                       ST95HF_SUPPORTED_PROT,
+                                                       ST95HF_CAPABILITIES,
+                                                       ST95HF_HEADROOM_LEN,
+                                                       ST95HF_TAILROOM_LEN);
+       if (!st95context->ddev) {
+               ret = -ENOMEM;
+               goto err_disable_regulator;
+       }
+
+       st95context->nfcdev = st95context->ddev->nfc_dev;
+       nfc_digital_set_parent_dev(st95context->ddev, &nfc_spi_dev->dev);
+
+       ret =  nfc_digital_register_device(st95context->ddev);
+       if (ret) {
+               dev_err(&st95context->nfcdev->dev, "st95hf registration failed\n");
+               goto err_free_digital_device;
+       }
+
+       /* store st95context in nfc device object */
+       nfc_digital_set_drvdata(st95context->ddev, st95context);
+
+       sema_init(&st95context->exchange_lock, 1);
+       mutex_init(&st95context->rm_lock);
+
+       return ret;
+
+err_free_digital_device:
+       nfc_digital_free_device(st95context->ddev);
+err_disable_regulator:
+       if (st95context->st95hf_supply)
+               regulator_disable(st95context->st95hf_supply);
+
+       return ret;
+}
+
+static int st95hf_remove(struct spi_device *nfc_spi_dev)
+{
+       int result = 0;
+       unsigned char reset_cmd = ST95HF_COMMAND_RESET;
+       struct st95hf_spi_context *spictx = dev_get_drvdata(&nfc_spi_dev->dev);
+
+       struct st95hf_context *stcontext = container_of(spictx,
+                                                       struct st95hf_context,
+                                                       spicontext);
+
+       mutex_lock(&stcontext->rm_lock);
+
+       nfc_digital_unregister_device(stcontext->ddev);
+       nfc_digital_free_device(stcontext->ddev);
+       stcontext->nfcdev_free = true;
+
+       mutex_unlock(&stcontext->rm_lock);
+
+       /* if last in_send_cmd's ISR is pending, wait for it to finish */
+       result = down_killable(&stcontext->exchange_lock);
+       if (result == -EINTR)
+               dev_err(&spictx->spidev->dev, "sleep for semaphore interrupted by signal\n");
+
+       /* next reset the ST95HF controller */
+       result = st95hf_spi_send(&stcontext->spicontext,
+                                &reset_cmd,
+                                ST95HF_RESET_CMD_LEN,
+                                ASYNC);
+       if (result) {
+               dev_err(&spictx->spidev->dev,
+                       "ST95HF reset failed in remove() err = %d\n", result);
+               return result;
+       }
+
+       /* wait for 3 ms to complete the controller reset process */
+       usleep_range(3000, 4000);
+
+       /* disable regulator */
+       if (stcontext->st95hf_supply)
+               regulator_disable(stcontext->st95hf_supply);
+
+       return result;
+}
+
+/* Register as SPI protocol driver */
+static struct spi_driver st95hf_driver = {
+       .driver = {
+               .name = "st95hf",
+               .owner = THIS_MODULE,
+       },
+       .id_table = st95hf_id,
+       .probe = st95hf_probe,
+       .remove = st95hf_remove,
+};
+
+module_spi_driver(st95hf_driver);
+
+MODULE_AUTHOR("Shikha Singh <shikha.singh@st.com>");
+MODULE_DESCRIPTION("ST NFC Transceiver ST95HF driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/nfc/st95hf/spi.c b/drivers/nfc/st95hf/spi.c
new file mode 100644 (file)
index 0000000..e2d3bbc
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * ----------------------------------------------------------------------------
+ * drivers/nfc/st95hf/spi.c function definitions for SPI communication
+ * ----------------------------------------------------------------------------
+ * Copyright (C) 2015 STMicroelectronics Pvt. Ltd. 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 "spi.h"
+
+/* Function to send user provided buffer to ST95HF through SPI */
+int st95hf_spi_send(struct st95hf_spi_context *spicontext,
+                   unsigned char *buffertx,
+                   int datalen,
+                   enum req_type reqtype)
+{
+       struct spi_message m;
+       int result = 0;
+       struct spi_device *spidev = spicontext->spidev;
+       struct spi_transfer tx_transfer = {
+               .tx_buf = buffertx,
+               .len = datalen,
+       };
+
+       mutex_lock(&spicontext->spi_lock);
+
+       if (reqtype == SYNC) {
+               spicontext->req_issync = true;
+               reinit_completion(&spicontext->done);
+       } else {
+               spicontext->req_issync = false;
+       }
+
+       spi_message_init(&m);
+       spi_message_add_tail(&tx_transfer, &m);
+
+       result = spi_sync(spidev, &m);
+       if (result) {
+               dev_err(&spidev->dev, "error: sending cmd to st95hf using SPI = %d\n",
+                       result);
+               mutex_unlock(&spicontext->spi_lock);
+               return result;
+       }
+
+       /* return for asynchronous or no-wait case */
+       if (reqtype == ASYNC) {
+               mutex_unlock(&spicontext->spi_lock);
+               return 0;
+       }
+
+       result = wait_for_completion_timeout(&spicontext->done,
+                                            msecs_to_jiffies(1000));
+       /* check for timeout or success */
+       if (!result) {
+               dev_err(&spidev->dev, "error: response not ready timeout\n");
+               result = -ETIMEDOUT;
+       } else {
+               result = 0;
+       }
+
+       mutex_unlock(&spicontext->spi_lock);
+
+       return result;
+}
+EXPORT_SYMBOL_GPL(st95hf_spi_send);
+
+/* Function to Receive command Response */
+int st95hf_spi_recv_response(struct st95hf_spi_context *spicontext,
+                            unsigned char *receivebuff)
+{
+       int len = 0;
+       struct spi_transfer tx_takedata;
+       struct spi_message m;
+       struct spi_device *spidev = spicontext->spidev;
+       unsigned char readdata_cmd = ST95HF_COMMAND_RECEIVE;
+       struct spi_transfer t[2] = {
+               {.tx_buf = &readdata_cmd, .len = 1,},
+               {.rx_buf = receivebuff, .len = 2, .cs_change = 1,},
+       };
+
+       int ret = 0;
+
+       memset(&tx_takedata, 0x0, sizeof(struct spi_transfer));
+
+       mutex_lock(&spicontext->spi_lock);
+
+       /* First spi transfer to know the length of valid data */
+       spi_message_init(&m);
+       spi_message_add_tail(&t[0], &m);
+       spi_message_add_tail(&t[1], &m);
+
+       ret = spi_sync(spidev, &m);
+       if (ret) {
+               dev_err(&spidev->dev, "spi_recv_resp, data length error = %d\n",
+                       ret);
+               mutex_unlock(&spicontext->spi_lock);
+               return ret;
+       }
+
+       /* As 2 bytes are already read */
+       len = 2;
+
+       /* Support of long frame */
+       if (receivebuff[0] & 0x60)
+               len += (((receivebuff[0] & 0x60) >> 5) << 8) | receivebuff[1];
+       else
+               len += receivebuff[1];
+
+       /* Now make a transfer to read only relevant bytes */
+       tx_takedata.rx_buf = &receivebuff[2];
+       tx_takedata.len = len - 2;
+
+       spi_message_init(&m);
+       spi_message_add_tail(&tx_takedata, &m);
+
+       ret = spi_sync(spidev, &m);
+
+       mutex_unlock(&spicontext->spi_lock);
+       if (ret) {
+               dev_err(&spidev->dev, "spi_recv_resp, data read error = %d\n",
+                       ret);
+               return ret;
+       }
+
+       return len;
+}
+EXPORT_SYMBOL_GPL(st95hf_spi_recv_response);
+
+int st95hf_spi_recv_echo_res(struct st95hf_spi_context *spicontext,
+                            unsigned char *receivebuff)
+{
+       unsigned char readdata_cmd = ST95HF_COMMAND_RECEIVE;
+       struct spi_transfer t[2] = {
+               {.tx_buf = &readdata_cmd, .len = 1,},
+               {.rx_buf = receivebuff, .len = 1,},
+       };
+       struct spi_message m;
+       struct spi_device *spidev = spicontext->spidev;
+       int ret = 0;
+
+       mutex_lock(&spicontext->spi_lock);
+
+       spi_message_init(&m);
+       spi_message_add_tail(&t[0], &m);
+       spi_message_add_tail(&t[1], &m);
+       ret = spi_sync(spidev, &m);
+
+       mutex_unlock(&spicontext->spi_lock);
+
+       if (ret)
+               dev_err(&spidev->dev, "recv_echo_res, data read error = %d\n",
+                       ret);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(st95hf_spi_recv_echo_res);
diff --git a/drivers/nfc/st95hf/spi.h b/drivers/nfc/st95hf/spi.h
new file mode 100644 (file)
index 0000000..552d220
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * ---------------------------------------------------------------------------
+ * drivers/nfc/st95hf/spi.h functions declarations for SPI communication
+ * ---------------------------------------------------------------------------
+ * Copyright (C) 2015 STMicroelectronics – 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 __LINUX_ST95HF_SPI_H
+#define __LINUX_ST95HF_SPI_H
+
+#include <linux/spi/spi.h>
+
+/* Basic ST95HF SPI CMDs */
+#define ST95HF_COMMAND_SEND    0x0
+#define ST95HF_COMMAND_RESET   0x1
+#define ST95HF_COMMAND_RECEIVE 0x2
+
+#define ST95HF_RESET_CMD_LEN   0x1
+
+/*
+ * structure to contain st95hf spi communication specific information.
+ * @req_issync: true for synchronous calls.
+ * @spidev: st95hf spi device object.
+ * @done: completion structure to wait for st95hf response
+ *     for synchronous calls.
+ * @spi_lock: mutex to allow only one spi transfer at a time.
+ */
+struct st95hf_spi_context {
+       bool req_issync;
+       struct spi_device *spidev;
+       struct completion done;
+       struct mutex spi_lock;
+};
+
+/* flag to differentiate synchronous & asynchronous spi request */
+enum req_type {
+       SYNC,
+       ASYNC,
+};
+
+int st95hf_spi_send(struct st95hf_spi_context *spicontext,
+                   unsigned char *buffertx,
+                   int datalen,
+                   enum req_type reqtype);
+
+int st95hf_spi_recv_response(struct st95hf_spi_context *spicontext,
+                            unsigned char *receivebuff);
+
+int st95hf_spi_recv_echo_res(struct st95hf_spi_context *spicontext,
+                            unsigned char *receivebuff);
+
+#endif