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