NFC: Add Broadcom 2079x NFC driver and spi phy

This is the first implementation of an NCI driver for a Broadcom 2079x
NFC chipset. In the current state, it has been tested on a Nexus10 and
can successfully initialize the chip, start_poll and detect a tag.

Signed-off-by: Eric Lapuyade <eric.lapuyade@intel.com>
diff --git a/drivers/nfc/Kconfig b/drivers/nfc/Kconfig
index 7929fac..275381c 100644
--- a/drivers/nfc/Kconfig
+++ b/drivers/nfc/Kconfig
@@ -73,4 +73,5 @@
 source "drivers/nfc/nfcmrvl/Kconfig"
 source "drivers/nfc/st21nfca/Kconfig"
 source "drivers/nfc/st21nfcb/Kconfig"
+source "drivers/nfc/bcm2079x/Kconfig"
 endmenu
diff --git a/drivers/nfc/Makefile b/drivers/nfc/Makefile
index 6b23a2c..cfe9fe6 100644
--- a/drivers/nfc/Makefile
+++ b/drivers/nfc/Makefile
@@ -4,6 +4,7 @@
 
 obj-$(CONFIG_NFC_PN544)		+= pn544/
 obj-$(CONFIG_NFC_MICROREAD)	+= microread/
+obj-$(CONFIG_NFC_BCM2079X)	+= bcm2079x/
 obj-$(CONFIG_NFC_PN533)		+= pn533.o
 obj-$(CONFIG_NFC_WILINK)	+= nfcwilink.o
 obj-$(CONFIG_NFC_MEI_PHY)	+= mei_phy.o
diff --git a/drivers/nfc/bcm2079x/Kconfig b/drivers/nfc/bcm2079x/Kconfig
new file mode 100644
index 0000000..8ff96b8
--- /dev/null
+++ b/drivers/nfc/bcm2079x/Kconfig
@@ -0,0 +1,23 @@
+config NFC_BCM2079X
+	tristate "Broadcom BCM2079x NFC driver"
+	depends on NFC_NCI
+	default n
+	---help---
+	  This module contains the main code for Broadcom BCM2079x
+	  NFC chipsets. It implements the chipset NCI logic and hooks into
+	  the NFC kernel APIs. Physical layers will register against it.
+
+	  To compile this driver as a module, choose m here. The module will
+	  be called bcm2079x.
+	  Say N if unsure.
+
+config NFC_BCM2079X_SPI
+	tristate "NFC Broadcom BCM2079x SPI support"
+	depends on NFC_BCM2079X && NFC_NCI_SPI
+	---help---
+	  This module adds support for the SPI interface of adapters using
+	  Broadcom BCM2079x.  Select this if your BCM2079x chipset
+	  is connected to a SPI physical Interface on your platform.
+
+	  If you choose to build a module, it'll be called bcm2079x_spi.
+	  Say N if unsure.
diff --git a/drivers/nfc/bcm2079x/Makefile b/drivers/nfc/bcm2079x/Makefile
new file mode 100644
index 0000000..133d2ee
--- /dev/null
+++ b/drivers/nfc/bcm2079x/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for Broadcom BCM2079x NCI based NFC driver
+#
+
+bcm2079x_spi-objs = spi.o
+
+obj-$(CONFIG_NFC_BCM2079X)     += bcm2079x.o
+obj-$(CONFIG_NFC_BCM2079X_SPI) += bcm2079x_spi.o
diff --git a/drivers/nfc/bcm2079x/bcm2079x.c b/drivers/nfc/bcm2079x/bcm2079x.c
new file mode 100644
index 0000000..c261702
--- /dev/null
+++ b/drivers/nfc/bcm2079x/bcm2079x.c
@@ -0,0 +1,127 @@
+/*
+ * NCI based Driver for Broadcom BCM2079x NFC Chip
+ *
+ * Copyright (C) 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.
+ */
+
+#include <linux/module.h>
+
+#include <net/nfc/nfc.h>
+#include <net/nfc/nci_core.h>
+
+#include "bcm2079x.h"
+
+struct bcm2079x_info {
+	struct nfc_phy_ops *phy_ops;
+	void *phy_id;
+
+	struct nci_dev *ndev;
+};
+
+static int bcm2079x_open(struct nci_dev *ndev)
+{
+	struct bcm2079x_info *info = nci_get_drvdata(ndev);
+
+	return info->phy_ops->enable(info->phy_id);
+}
+
+static int bcm2079x_close(struct nci_dev *ndev)
+{
+	struct bcm2079x_info *info = nci_get_drvdata(ndev);
+
+	info->phy_ops->disable(info->phy_id);
+
+	return 0;
+}
+
+static int bcm2079x_send(struct nci_dev *ndev, struct sk_buff *skb)
+{
+	struct bcm2079x_info *info = nci_get_drvdata(ndev);
+
+	return info->phy_ops->write(info->phy_id, skb);
+}
+
+static struct nci_ops bcm2079x_ops = {
+	.open = bcm2079x_open,
+	.close = bcm2079x_close,
+	.send = bcm2079x_send,
+};
+
+int bcm2079x_probe(void *phy_id, struct nfc_phy_ops *phy_ops,
+		   int phy_headroom, int phy_tailroom, struct nci_dev **ndev)
+{
+	struct bcm2079x_info *info;
+	u32 protocols;
+	int r;
+
+	info = kzalloc(sizeof(struct bcm2079x_info), GFP_KERNEL);
+	if (!info) {
+		pr_err("Cannot allocate memory for bcm2079x_info.\n");
+		r = -ENOMEM;
+		goto err_info_alloc;
+	}
+
+	info->phy_ops = phy_ops;
+	info->phy_id = phy_id;
+
+	protocols = NFC_PROTO_JEWEL_MASK |
+		    NFC_PROTO_MIFARE_MASK |
+		    NFC_PROTO_FELICA_MASK |
+		    NFC_PROTO_ISO14443_MASK |
+		    NFC_PROTO_ISO14443_B_MASK |
+		    NFC_PROTO_NFC_DEP_MASK;
+
+	info->ndev = nci_allocate_device(&bcm2079x_ops, protocols, phy_headroom,
+					 phy_tailroom);
+	if (!info->ndev) {
+		pr_err("Cannot allocate nfc ndev.\n");
+		r = -ENOMEM;
+		goto err_alloc_ndev;
+	}
+
+	nci_set_drvdata(info->ndev, info);
+
+	r = nci_register_device(info->ndev);
+	if (r)
+		goto err_regdev;
+
+	*ndev = info->ndev;
+
+	return 0;
+
+err_regdev:
+	nci_free_device(info->ndev);
+
+err_alloc_ndev:
+	kfree(info);
+
+err_info_alloc:
+	return r;
+}
+EXPORT_SYMBOL(bcm2079x_probe);
+
+void bcm2079x_remove(struct nci_dev *ndev)
+{
+	struct bcm2079x_info *info = nci_get_drvdata(ndev);
+
+	nci_unregister_device(ndev);
+	nci_free_device(ndev);
+	kfree(info);
+}
+EXPORT_SYMBOL(bcm2079x_remove);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION(DRIVER_DESC);
diff --git a/drivers/nfc/bcm2079x/bcm2079x.h b/drivers/nfc/bcm2079x/bcm2079x.h
new file mode 100644
index 0000000..a5c0de8
--- /dev/null
+++ b/drivers/nfc/bcm2079x/bcm2079x.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.
+ */
+
+#ifndef __LOCAL_BCM2079X_H_
+#define __LOCAL_BCM2079X_H_
+
+#include <net/nfc/nci_core.h>
+
+#define DRIVER_DESC "NFC driver for BCM2079x"
+
+#define FRAME_TYPE_NCI 0x10
+
+int bcm2079x_probe(void *phy_id, struct nfc_phy_ops *phy_ops,
+		   int phy_headroom, int phy_tailroom, struct nci_dev **ndev);
+
+void bcm2079x_remove(struct nci_dev *ndev);
+
+#endif /* __LOCAL_BCM2079X_H_ */
diff --git a/drivers/nfc/bcm2079x/spi.c b/drivers/nfc/bcm2079x/spi.c
new file mode 100644
index 0000000..5033a7f
--- /dev/null
+++ b/drivers/nfc/bcm2079x/spi.c
@@ -0,0 +1,262 @@
+/*
+ * SPI phy driver for Broadcom BCM2079x NFC Chip
+ *
+ * Copyright (C) 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/gpio.h>
+#include <linux/platform_data/bcm2079x.h>
+#include <net/nfc/nfc.h>
+
+#include "bcm2079x.h"
+
+#define STATE_HIGH		1
+#define STATE_LOW		0
+
+#define NFC_REQ_ACTIVE_STATE	STATE_LOW
+
+struct bcm2079x_spi_phy {
+	struct spi_device *spi;
+	struct nci_spi *nspi;
+	struct nci_dev *ndev;
+
+	struct mutex rw_mutex;
+	struct completion *write_handshake_completion;
+
+	unsigned int en_gpio;
+	unsigned int irq_gpio;
+	unsigned int wake_gpio;
+};
+
+static irqreturn_t bcm2079x_spi_irq_thread_handler(int irq, void *phy_id)
+{
+	struct bcm2079x_spi_phy *phy = phy_id;
+	struct sk_buff *skb;
+	int r = 0;
+
+	mutex_lock(&phy->rw_mutex);
+
+	if (phy->write_handshake_completion) {
+		/*
+		 * If we're currently handshaking signals to synchronize
+		 * sending data, this interrupt is the GO.
+		 * Note the possible race condition: interrupt can have
+		 * occurred before bcm2079x_spi_write was started and thus
+		 * be a real receive interrupt. In that case, the NCI spec
+		 * says that the host always wins, so we always go with the
+		 * send.
+		 */
+		complete(phy->write_handshake_completion);
+		phy->write_handshake_completion = NULL;
+	} else {
+		skb = nci_spi_read(phy->nspi);
+		if (!skb)
+			r = -EIO;
+		if (r) {
+			/* TODO: device should probably go into a dead state */
+			dev_err(&phy->spi->dev,
+				"%s: nci_spi_recv_frame result=%d\n", __func__,
+				r);
+		} else {
+			/* remove frame type */
+			skb_pull(skb, 1);
+			r = nci_recv_frame(phy->ndev, skb);
+		}
+	}
+
+	mutex_unlock(&phy->rw_mutex);
+
+	return IRQ_HANDLED;
+}
+
+static int bcm2079x_spi_write(void *phy_id, struct sk_buff *skb)
+{
+	struct bcm2079x_spi_phy *phy = phy_id;
+	struct completion write_handshake_completion;
+	int r;
+
+	init_completion(&write_handshake_completion);
+
+	mutex_lock(&phy->rw_mutex);
+	phy->write_handshake_completion = &write_handshake_completion;
+	mutex_unlock(&phy->rw_mutex);
+
+	*skb_push(skb, 1) = FRAME_TYPE_NCI;
+	r = nci_spi_send(phy->nspi, &write_handshake_completion, skb);
+
+	mutex_lock(&phy->rw_mutex);
+	phy->write_handshake_completion = NULL;
+	mutex_unlock(&phy->rw_mutex);
+
+	return r;
+}
+
+static int bcm2079x_spi_enable(void *phy_id)
+{
+	struct bcm2079x_spi_phy *phy = phy_id;
+
+	gpio_set_value(phy->en_gpio, 1);
+
+	return 0;
+}
+
+static void bcm2079x_spi_disable(void *phy_id)
+{
+	struct bcm2079x_spi_phy *phy = phy_id;
+
+	gpio_set_value(phy->en_gpio, 0);
+}
+
+static struct nfc_phy_ops spi_phy_ops = {
+	.write = bcm2079x_spi_write,
+	.enable = bcm2079x_spi_enable,
+	.disable = bcm2079x_spi_disable,
+};
+
+static int bcm2079x_spi_probe(struct spi_device *spi)
+{
+	int r;
+	struct bcm2079x_spi_phy *phy;
+	struct bcm2079x_platform_data *pdata;
+	unsigned int spi_delay_us;
+
+	pdata = spi->dev.platform_data;
+
+	dev_info(&spi->dev, "%s\n", __func__);
+
+	if (pdata == NULL) {
+		dev_err(&spi->dev, "%s: no platform data\n", __func__);
+		return -ENODEV;
+	}
+
+	phy = devm_kzalloc(&spi->dev, sizeof(struct bcm2079x_spi_phy),
+			   GFP_KERNEL);
+	if (!phy) {
+		dev_err(&spi->dev,
+			"%s: cannot allocate bcm2079x_spi_phy\n", __func__);
+		return -ENOMEM;
+	}
+
+	mutex_init(&phy->rw_mutex);
+
+	phy->spi = spi;
+	spi_set_drvdata(spi, phy);
+
+	r = devm_gpio_request(&spi->dev, pdata->irq_gpio, "nfc_spi_int");
+	if (r) {
+		dev_err(&spi->dev,
+			"%s: cannot request irq_gpio (r=%d)\n", __func__, r);
+		goto exit_free_mutex;
+	}
+
+	r = devm_gpio_request(&spi->dev, pdata->en_gpio, "nfc_en");
+	if (r) {
+		dev_err(&spi->dev,
+			"%s: cannot request en_gpio (r=%d)\n", __func__, r);
+		goto exit_free_mutex;
+	}
+
+	/* Wake pin can be joined with SPI_CSN */
+	if (pdata->wake_gpio > 0) {
+		r = devm_gpio_request(&spi->dev, pdata->wake_gpio, "nfc_wake");
+		if (r) {
+			dev_err(&spi->dev,
+				"%s: cannot request wake_gpio (r=%d)\n",
+				__func__, r);
+			goto exit_free_mutex;
+		}
+		gpio_direction_output(pdata->wake_gpio, 0);
+		gpio_set_value(pdata->wake_gpio, 0);
+	}
+
+	gpio_direction_output(pdata->en_gpio, 0);
+	gpio_direction_input(pdata->irq_gpio);
+	gpio_set_value(pdata->en_gpio, 0);
+
+	phy->wake_gpio = pdata->wake_gpio;
+	phy->irq_gpio = pdata->irq_gpio;
+	phy->en_gpio = pdata->en_gpio;
+
+	r = request_threaded_irq(spi->irq, NULL,
+				 bcm2079x_spi_irq_thread_handler,
+				 IRQF_TRIGGER_FALLING, spi->modalias, phy);
+	if (r < 0) {
+		dev_err(&spi->dev,
+			"%s: cannot request irq (r=%d)\n", __func__, r);
+		goto exit_free_mutex;
+	}
+
+	/* tailroom would be NCI_SPI_CRC_LEN if spi ack mode was crc enabled */
+	r = bcm2079x_probe(phy, &spi_phy_ops, NCI_SPI_HDR_LEN + 1, 0,
+			   &phy->ndev);
+	if (r)
+		goto exit_free_irq;
+
+	spi_delay_us = spi->max_speed_hz == 0 ? 5 : 1000000 / spi->max_speed_hz;
+	if (spi_delay_us == 0)
+		spi_delay_us = 1000;
+
+	phy->nspi = nci_spi_allocate_spi(spi, NCI_SPI_CRC_DISABLED,
+					 spi_delay_us, phy->ndev);
+	if (phy->nspi == NULL) {
+		r = -ENOMEM;
+		goto exit_remove;
+	}
+
+	return 0;
+
+exit_remove:
+	bcm2079x_remove(phy->ndev);
+
+exit_free_irq:
+	free_irq(spi->irq, phy);
+
+exit_free_mutex:
+	mutex_destroy(&phy->rw_mutex);
+
+	return r;
+}
+
+static int bcm2079x_spi_remove(struct spi_device *spi)
+{
+	struct bcm2079x_spi_phy *phy;
+
+	phy = (struct bcm2079x_spi_phy *) spi_get_drvdata(spi);
+
+	bcm2079x_remove(phy->ndev);
+
+	free_irq(spi->irq, phy);
+
+	mutex_destroy(&phy->rw_mutex);
+
+	return 0;
+}
+
+static struct spi_driver bcm2079x_driver = {
+	.driver = {
+		   .owner = THIS_MODULE,
+		   .name = "bcm2079x-spi",
+		   },
+	.probe = bcm2079x_spi_probe,
+	.remove = bcm2079x_spi_remove,
+};
+
+module_spi_driver(bcm2079x_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION(DRIVER_DESC);
diff --git a/include/linux/platform_data/bcm2079x.h b/include/linux/platform_data/bcm2079x.h
new file mode 100644
index 0000000..9c7bb5c
--- /dev/null
+++ b/include/linux/platform_data/bcm2079x.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2012 Broadcom Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _BCM2079X_H
+#define _BCM2079X_H
+
+struct bcm2079x_platform_data {
+	unsigned int irq_gpio;
+	unsigned int en_gpio;
+	unsigned int wake_gpio;
+};
+
+#endif