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