[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <tencent_ACA99123220028D078B20189509FD204BC0A@qq.com>
Date: Fri, 6 Feb 2026 01:31:10 -0500
From: 434779359@...com
To: Linus Walleij <linusw@...nel.org>,
Bartosz Golaszewski <brgl@...nel.org>,
xuchen <434779359@...com>
Cc: linux-kernel@...r.kernel.org,
netdev@...r.kernel.org,
linux-gpio@...r.kernel.org,
xuchen <bright.xu@...ot.com>
Subject: [PATCH] nfc: supply: Add PN7160 drivers This patch adds support for the PN7160 ICs used in Qualcomm reference designs.
From: xuchen <bright.xu@...ot.com>
Signed-off-by: xuchen <434779359@...com>
---
drivers/nfc/pn7160/Kconfig | 40 +++
drivers/nfc/pn7160/Makefile | 6 +
drivers/nfc/pn7160/common.c | 371 ++++++++++++++++++++++++
drivers/nfc/pn7160/common.h | 36 +++
drivers/nfc/pn7160/i2c_drv.c | 531 ++++++++++++++++++++++++++++++++++
drivers/nfc/pn7160/i2c_drv.h | 26 ++
drivers/nfc/pn7160/platform.h | 164 +++++++++++
7 files changed, 1174 insertions(+)
create mode 100644 drivers/nfc/pn7160/Kconfig
create mode 100644 drivers/nfc/pn7160/Makefile
create mode 100644 drivers/nfc/pn7160/common.c
create mode 100644 drivers/nfc/pn7160/common.h
create mode 100644 drivers/nfc/pn7160/i2c_drv.c
create mode 100644 drivers/nfc/pn7160/i2c_drv.h
create mode 100644 drivers/nfc/pn7160/platform.h
diff --git a/drivers/nfc/pn7160/Kconfig b/drivers/nfc/pn7160/Kconfig
new file mode 100644
index 000000000000..fb497bc33059
--- /dev/null
+++ b/drivers/nfc/pn7160/Kconfig
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# near field communication configuration
+#
+
+config NXP_NFC_I2C
+ tristate "NFC I2C Slave driver for NXP-NFCC"
+ depends on I2C
+ help
+ This enables the NFC driver for PN71xx based devices.
+ This is for I2C connected version. NCI protocol logic
+ resides in the usermode and it has no other NFC dependencies.
+
+ If unsure, say N.
+
+config NXP_NFC_SPI
+ tristate "NFC SPI Slave driver for NXP-NFCC"
+ depends on SPI
+ help
+ This enables the NFC driver for PN71xx based devices.
+ This is for SPI connected version. NCI protocol logic
+ resides in the usermode and it has no other NFC dependencies.
+
+ If unsure, say N.
+
+config NXP_NFC_RECOVERY
+ bool "NXP based NFC minimal FW update support"
+ depends on NXP_NFC_I2C && I2C
+ default y
+ help
+ This enables NFC minimal FW update.
+ This feature allows updating the firmware of NXP NFC controllers
+ in recovery mode. It is required for field updates and bug fixes.
+ The driver will handle the download mode and firmware transfer
+ when this option is enabled.
+
+ If unsure, say N.
+
+source "drivers/nfc/pn7160/Kconfig"
+endif
diff --git a/drivers/nfc/pn7160/Makefile b/drivers/nfc/pn7160/Makefile
new file mode 100644
index 000000000000..8c6f670eaa7a
--- /dev/null
+++ b/drivers/nfc/pn7160/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Makefile for nfc devices
+#
+obj-m += nxpnfc_i2c.o
+nxpnfc_i2c-objs := common.o i2c_drv.o
diff --git a/drivers/nfc/pn7160/common.c b/drivers/nfc/pn7160/common.c
new file mode 100644
index 000000000000..cd433912764b
--- /dev/null
+++ b/drivers/nfc/pn7160/common.c
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2019-2021 NXP
+ */
+
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+
+#include "common.h"
+
+int nfc_parse_dt(struct device *dev, struct platform_configs *nfc_configs,
+ uint8_t interface)
+{
+ struct device_node *np = dev->of_node;
+ struct platform_gpio *nfc_gpio = &nfc_configs->gpio;
+
+ if (!np) {
+ pr_err("%s: nfc of_node NULL\n", __func__);
+ return -EINVAL;
+ }
+
+ nfc_gpio->irq = -EINVAL;
+ nfc_gpio->dwl_req = -EINVAL;
+ nfc_gpio->ven = -EINVAL;
+
+ /* irq required for i2c based chips only */
+ if (interface == PLATFORM_IF_I2C || interface == PLATFORM_IF_SPI) {
+ nfc_gpio->irq = of_get_named_gpio(np, DTS_IRQ_GPIO_STR, 0);
+ if ((!gpio_is_valid(nfc_gpio->irq))) {
+ pr_err("%s: irq gpio invalid %d\n", __func__,
+ nfc_gpio->irq);
+ return -EINVAL;
+ }
+ pr_info("%s: irq %d\n", __func__, nfc_gpio->irq);
+ }
+ nfc_gpio->ven = of_get_named_gpio(np, DTS_VEN_GPIO_STR, 0);
+ if ((!gpio_is_valid(nfc_gpio->ven))) {
+ pr_err("%s: ven gpio invalid %d\n", __func__, nfc_gpio->ven);
+ return -EINVAL;
+ }
+ /* some products like sn220 does not required fw dwl pin */
+ nfc_gpio->dwl_req = of_get_named_gpio(np, DTS_FWDN_GPIO_STR, 0);
+ if ((!gpio_is_valid(nfc_gpio->dwl_req)))
+ pr_warn("%s: dwl_req gpio invalid %d\n", __func__,
+ nfc_gpio->dwl_req);
+
+ pr_info("%s: %d, %d, %d\n", __func__, nfc_gpio->irq, nfc_gpio->ven,
+ nfc_gpio->dwl_req);
+ return 0;
+}
+EXPORT_SYMBOL(nfc_parse_dt);
+
+void set_valid_gpio(int gpio, int value)
+{
+ if (gpio_is_valid(gpio)) {
+ pr_debug("%s: gpio %d value %d\n", __func__, gpio, value);
+ gpio_set_value(gpio, value);
+ /* hardware dependent delay */
+ usleep_range(NFC_GPIO_SET_WAIT_TIME_US,
+ NFC_GPIO_SET_WAIT_TIME_US + 100);
+ }
+}
+EXPORT_SYMBOL(set_valid_gpio);
+
+int get_valid_gpio(int gpio)
+{
+ int value = -EINVAL;
+
+ if (gpio_is_valid(gpio)) {
+ value = gpio_get_value(gpio);
+ pr_debug("%s: gpio %d value %d\n", __func__, gpio, value);
+ }
+ return value;
+}
+EXPORT_SYMBOL(get_valid_gpio);
+
+void gpio_set_ven(struct nfc_dev *nfc_dev, int value)
+{
+ struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio;
+
+ if (gpio_get_value(nfc_gpio->ven) != value) {
+ pr_debug("%s: value %d\n", __func__, value);
+
+ gpio_set_value(nfc_gpio->ven, value);
+ /* hardware dependent delay */
+ usleep_range(NFC_GPIO_SET_WAIT_TIME_US,
+ NFC_GPIO_SET_WAIT_TIME_US + 100);
+ }
+}
+EXPORT_SYMBOL(gpio_set_ven);
+
+int configure_gpio(unsigned int gpio, int flag)
+{
+ int ret;
+
+ pr_debug("%s: nfc gpio [%d] flag [%01x]\n", __func__, gpio, flag);
+ if (gpio_is_valid(gpio)) {
+ ret = gpio_request(gpio, "nfc_gpio");
+ if (ret) {
+ pr_err("%s: unable to request nfc gpio [%d]\n",
+ __func__, gpio);
+ return ret;
+ }
+ /* set direction and value for output pin */
+ if (flag & GPIO_OUTPUT) {
+ ret = gpio_direction_output(gpio, (GPIO_HIGH & flag));
+ pr_debug("%s: nfc o/p gpio %d level %d\n", __func__,
+ gpio, gpio_get_value(gpio));
+ } else {
+ ret = gpio_direction_input(gpio);
+ pr_debug("%s: nfc i/p gpio %d\n", __func__, gpio);
+ }
+
+ if (ret) {
+ pr_err("%s: unable to set direction for nfc gpio [%d]\n",
+ __func__, gpio);
+ gpio_free(gpio);
+ return ret;
+ }
+ /* Consider value as control for input IRQ pin */
+ if (flag & GPIO_IRQ) {
+ ret = gpio_to_irq(gpio);
+ if (ret < 0) {
+ pr_err("%s: unable to set irq [%d]\n", __func__,
+ gpio);
+ gpio_free(gpio);
+ return ret;
+ }
+ pr_debug("%s: gpio_to_irq successful [%d]\n", __func__,
+ gpio);
+ return ret;
+ }
+ } else {
+ pr_err("%s: invalid gpio\n", __func__);
+ ret = -EINVAL;
+ }
+ return ret;
+}
+EXPORT_SYMBOL(configure_gpio);
+
+void gpio_free_all(struct nfc_dev *nfc_dev)
+{
+ struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio;
+
+ if (gpio_is_valid(nfc_gpio->dwl_req))
+ gpio_free(nfc_gpio->dwl_req);
+
+ if (gpio_is_valid(nfc_gpio->irq))
+ gpio_free(nfc_gpio->irq);
+
+ if (gpio_is_valid(nfc_gpio->ven))
+ gpio_free(nfc_gpio->ven);
+}
+EXPORT_SYMBOL(gpio_free_all);
+
+void nfc_misc_unregister(struct nfc_dev *nfc_dev, int count)
+{
+ pr_debug("%s: entry\n", __func__);
+ device_destroy(nfc_dev->nfc_class, nfc_dev->devno);
+ cdev_del(&nfc_dev->c_dev);
+ class_destroy(nfc_dev->nfc_class);
+ unregister_chrdev_region(nfc_dev->devno, count);
+}
+EXPORT_SYMBOL(nfc_misc_unregister);
+
+int nfc_misc_register(struct nfc_dev *nfc_dev,
+ const struct file_operations *nfc_fops, int count,
+ char *devname, char *classname)
+{
+ int ret = 0;
+
+ ret = alloc_chrdev_region(&nfc_dev->devno, 0, count, devname);
+ if (ret < 0) {
+ pr_err("%s: failed to alloc chrdev region ret %d\n", __func__,
+ ret);
+ return ret;
+ }
+ nfc_dev->nfc_class = class_create(classname);
+ if (IS_ERR(nfc_dev->nfc_class)) {
+ ret = PTR_ERR(nfc_dev->nfc_class);
+ pr_err("%s: failed to register device class ret %d\n", __func__,
+ ret);
+ unregister_chrdev_region(nfc_dev->devno, count);
+ return ret;
+ }
+ cdev_init(&nfc_dev->c_dev, nfc_fops);
+ ret = cdev_add(&nfc_dev->c_dev, nfc_dev->devno, count);
+ if (ret < 0) {
+ pr_err("%s: failed to add cdev ret %d\n", __func__, ret);
+ class_destroy(nfc_dev->nfc_class);
+ unregister_chrdev_region(nfc_dev->devno, count);
+ return ret;
+ }
+ nfc_dev->nfc_device = device_create(nfc_dev->nfc_class, NULL,
+ nfc_dev->devno, nfc_dev, devname);
+ if (IS_ERR(nfc_dev->nfc_device)) {
+ ret = PTR_ERR(nfc_dev->nfc_device);
+ pr_err("%s: failed to create the device ret %d\n", __func__,
+ ret);
+ cdev_del(&nfc_dev->c_dev);
+ class_destroy(nfc_dev->nfc_class);
+ unregister_chrdev_region(nfc_dev->devno, count);
+ return ret;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(nfc_misc_register);
+
+/**
+ * nfc_ioctl_power_states() - power control
+ * @nfc_dev: nfc device data structure
+ * @arg: mode that we want to move to
+ *
+ * Device power control. Depending on the arg value, device moves to
+ * different states, refer platform.h for args
+ *
+ * Return: -ENOIOCTLCMD if arg is not supported, 0 in any other case
+ */
+static int nfc_ioctl_power_states(struct nfc_dev *nfc_dev, unsigned long arg)
+{
+ int ret = 0;
+ struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio;
+
+ if (arg == NFC_POWER_OFF) {
+ /*
+ * We are attempting a hardware reset so let us disable
+ * interrupts to avoid spurious notifications to upper
+ * layers.
+ */
+ nfc_dev->nfc_disable_intr(nfc_dev);
+ set_valid_gpio(nfc_gpio->dwl_req, 0);
+ gpio_set_ven(nfc_dev, 0);
+ nfc_dev->nfc_ven_enabled = false;
+ } else if (arg == NFC_POWER_ON) {
+ nfc_dev->nfc_enable_intr(nfc_dev);
+ set_valid_gpio(nfc_gpio->dwl_req, 0);
+
+ gpio_set_ven(nfc_dev, 1);
+ nfc_dev->nfc_ven_enabled = true;
+ } else if (arg == NFC_FW_DWL_VEN_TOGGLE) {
+ /*
+ * We are switching to download Mode, toggle the enable pin
+ * in order to set the NFCC in the new mode
+ */
+ nfc_dev->nfc_disable_intr(nfc_dev);
+ set_valid_gpio(nfc_gpio->dwl_req, 1);
+ nfc_dev->nfc_state = NFC_STATE_FW_DWL;
+ gpio_set_ven(nfc_dev, 0);
+ gpio_set_ven(nfc_dev, 1);
+ nfc_dev->nfc_enable_intr(nfc_dev);
+ } else if (arg == NFC_FW_DWL_HIGH) {
+ /*
+ * Setting firmware download gpio to HIGH
+ * before FW download start
+ */
+ set_valid_gpio(nfc_gpio->dwl_req, 1);
+ nfc_dev->nfc_state = NFC_STATE_FW_DWL;
+
+ } else if (arg == NFC_VEN_FORCED_HARD_RESET) {
+ nfc_dev->nfc_disable_intr(nfc_dev);
+ gpio_set_ven(nfc_dev, 0);
+ gpio_set_ven(nfc_dev, 1);
+ nfc_dev->nfc_enable_intr(nfc_dev);
+ } else if (arg == NFC_FW_DWL_LOW) {
+ /*
+ * Setting firmware download gpio to LOW
+ * FW download finished
+ */
+ set_valid_gpio(nfc_gpio->dwl_req, 0);
+ nfc_dev->nfc_state = NFC_STATE_NCI;
+ } else {
+ pr_err("%s: bad arg %lu\n", __func__, arg);
+ ret = -ENOIOCTLCMD;
+ }
+ return ret;
+}
+
+/**
+ * nfc_dev_ioctl - used to set or get data from upper layer.
+ * @pfile file node for opened device.
+ * @cmd ioctl type from upper layer.
+ * @arg ioctl arg from upper layer.
+ *
+ * NFC and ESE Device power control, based on the argument value
+ *
+ * Return: -ENOIOCTLCMD if arg is not supported, 0 or other in any other case
+ */
+long nfc_dev_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
+{
+ int ret = 0;
+ struct nfc_dev *nfc_dev = pfile->private_data;
+
+ if (!nfc_dev)
+ return -ENODEV;
+
+ pr_debug("%s: cmd = %x arg = %zx\n", __func__, cmd, arg);
+ if (cmd == NFC_SET_PWR) {
+ ret = nfc_ioctl_power_states(nfc_dev, arg);
+ } else {
+ pr_err("%s: bad cmd %lu\n", __func__, arg);
+ ret = -ENOIOCTLCMD;
+ }
+ return ret;
+}
+EXPORT_SYMBOL(nfc_dev_ioctl);
+
+int nfc_dev_open(struct inode *inode, struct file *filp)
+{
+ struct nfc_dev *nfc_dev =
+ container_of(inode->i_cdev, struct nfc_dev, c_dev);
+
+ pr_debug("%s: %d, %d\n", __func__, imajor(inode), iminor(inode));
+
+ mutex_lock(&nfc_dev->dev_ref_mutex);
+
+ filp->private_data = nfc_dev;
+
+ if (nfc_dev->dev_ref_count == 0) {
+ set_valid_gpio(nfc_dev->configs.gpio.dwl_req, 0);
+
+ nfc_dev->nfc_enable_intr(nfc_dev);
+ }
+ nfc_dev->dev_ref_count = nfc_dev->dev_ref_count + 1;
+ mutex_unlock(&nfc_dev->dev_ref_mutex);
+ return 0;
+}
+EXPORT_SYMBOL(nfc_dev_open);
+
+int nfc_dev_close(struct inode *inode, struct file *filp)
+{
+ struct nfc_dev *nfc_dev =
+ container_of(inode->i_cdev, struct nfc_dev, c_dev);
+
+ pr_debug("%s: %d, %d\n", __func__, imajor(inode), iminor(inode));
+ mutex_lock(&nfc_dev->dev_ref_mutex);
+ if (nfc_dev->dev_ref_count == 1) {
+ nfc_dev->nfc_disable_intr(nfc_dev);
+ set_valid_gpio(nfc_dev->configs.gpio.dwl_req, 0);
+ }
+ if (nfc_dev->dev_ref_count > 0)
+ nfc_dev->dev_ref_count = nfc_dev->dev_ref_count - 1;
+ filp->private_data = NULL;
+
+ mutex_unlock(&nfc_dev->dev_ref_mutex);
+ return 0;
+}
+EXPORT_SYMBOL(nfc_dev_close);
+
+int validate_nfc_state_nci(struct nfc_dev *nfc_dev)
+{
+ struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio;
+
+ if (!gpio_get_value(nfc_gpio->ven)) {
+ pr_err("%s: ven low - nfcc powered off\n", __func__);
+ return -ENODEV;
+ }
+ if (get_valid_gpio(nfc_gpio->dwl_req) == 1) {
+ pr_err("%s: fw download in-progress\n", __func__);
+ return -EBUSY;
+ }
+ if (nfc_dev->nfc_state != NFC_STATE_NCI) {
+ pr_err("%s: fw download state\n", __func__);
+ return -EBUSY;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(validate_nfc_state_nci);
diff --git a/drivers/nfc/pn7160/common.h b/drivers/nfc/pn7160/common.h
new file mode 100644
index 000000000000..2451db295fc8
--- /dev/null
+++ b/drivers/nfc/pn7160/common.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2019-2021 NXP
+ */
+
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include "platform.h"
+
+/* 函数声明 */
+int nfc_dev_open(struct inode *inode, struct file *filp);
+int nfc_dev_close(struct inode *inode, struct file *filp);
+long nfc_dev_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg);
+int nfc_parse_dt(struct device *dev, struct platform_configs *nfc_configs,
+ uint8_t interface);
+int nfc_misc_register(struct nfc_dev *nfc_dev,
+ const struct file_operations *nfc_fops, int count,
+ char *devname, char *classname);
+void nfc_misc_unregister(struct nfc_dev *nfc_dev, int count);
+int configure_gpio(unsigned int gpio, int flag);
+void gpio_set_ven(struct nfc_dev *nfc_dev, int value);
+void gpio_free_all(struct nfc_dev *nfc_dev);
+int validate_nfc_state_nci(struct nfc_dev *nfc_dev);
+void set_valid_gpio(int gpio, int value);
+int get_valid_gpio(int gpio);
+
+/* I2C specific functions */
+int i2c_disable_irq(struct nfc_dev *dev);
+int i2c_enable_irq(struct nfc_dev *dev);
+int i2c_read(struct nfc_dev *nfc_dev, char *buf, size_t count, int timeout);
+int i2c_write(struct nfc_dev *nfc_dev, const char *buf, size_t count,
+ int max_retry_cnt);
+
+#endif /* _COMMON_H_ */
diff --git a/drivers/nfc/pn7160/i2c_drv.c b/drivers/nfc/pn7160/i2c_drv.c
new file mode 100644
index 000000000000..859441472343
--- /dev/null
+++ b/drivers/nfc/pn7160/i2c_drv.c
@@ -0,0 +1,531 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2013-2021 NXP
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/pm.h>
+#include <linux/fs.h>
+
+#include "common.h"
+#include "i2c_drv.h"
+
+/**
+ * i2c_disable_irq()
+ *
+ * Check if interrupt is disabled or not
+ * and disable interrupt
+ *
+ * Return: int
+ */
+int i2c_disable_irq(struct nfc_dev *dev)
+{
+ struct i2c_dev *i2c_dev = dev->platform_data;
+ unsigned long flags;
+
+ if (!i2c_dev)
+ return -EINVAL;
+
+ spin_lock_irqsave(&i2c_dev->irq_enabled_lock, flags);
+ if (i2c_dev->irq_enabled) {
+ disable_irq_nosync(i2c_dev->client->irq);
+ i2c_dev->irq_enabled = false;
+ }
+ spin_unlock_irqrestore(&i2c_dev->irq_enabled_lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(i2c_disable_irq);
+
+/**
+ * i2c_enable_irq()
+ *
+ * Check if interrupt is enabled or not
+ * and enable interrupt
+ *
+ * Return: int
+ */
+int i2c_enable_irq(struct nfc_dev *dev)
+{
+ struct i2c_dev *i2c_dev = dev->platform_data;
+ unsigned long flags;
+
+ if (!i2c_dev)
+ return -EINVAL;
+
+ spin_lock_irqsave(&i2c_dev->irq_enabled_lock, flags);
+ if (!i2c_dev->irq_enabled) {
+ i2c_dev->irq_enabled = true;
+ enable_irq(i2c_dev->client->irq);
+ }
+ spin_unlock_irqrestore(&i2c_dev->irq_enabled_lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(i2c_enable_irq);
+
+static irqreturn_t i2c_irq_handler(int irq, void *dev_id)
+{
+ struct nfc_dev *nfc_dev = dev_id;
+ struct i2c_dev *i2c_dev = nfc_dev->platform_data;
+
+ if (!i2c_dev)
+ return IRQ_NONE;
+
+ if (device_may_wakeup(&i2c_dev->client->dev))
+ pm_wakeup_event(&i2c_dev->client->dev, WAKEUP_SRC_TIMEOUT);
+
+ i2c_disable_irq(nfc_dev);
+ wake_up(&nfc_dev->read_wq);
+
+ return IRQ_HANDLED;
+}
+
+int i2c_read(struct nfc_dev *nfc_dev, char *buf, size_t count, int timeout)
+{
+ struct i2c_dev *i2c_dev = nfc_dev->platform_data;
+ struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio;
+ int ret = -EINVAL;
+
+ pr_debug("%s: reading %zu bytes.\n", __func__, count);
+
+ if (!i2c_dev)
+ return -EINVAL;
+
+ if (timeout > NCI_CMD_RSP_TIMEOUT_MS)
+ timeout = NCI_CMD_RSP_TIMEOUT_MS;
+
+ if (count > MAX_NCI_BUFFER_SIZE)
+ count = MAX_NCI_BUFFER_SIZE;
+
+ if (!gpio_get_value(nfc_gpio->irq)) {
+ while (1) {
+ ret = 0;
+ if (!i2c_dev->irq_enabled) {
+ i2c_dev->irq_enabled = true;
+ enable_irq(i2c_dev->client->irq);
+ }
+ if (!gpio_get_value(nfc_gpio->irq)) {
+ if (timeout) {
+ ret = wait_event_interruptible_timeout(
+ nfc_dev->read_wq,
+ !i2c_dev->irq_enabled,
+ msecs_to_jiffies(timeout));
+
+ if (ret <= 0) {
+ pr_err("%s: timeout error\n",
+ __func__);
+ goto err;
+ }
+ } else {
+ ret = wait_event_interruptible(
+ nfc_dev->read_wq,
+ !i2c_dev->irq_enabled);
+ if (ret) {
+ pr_err("%s: err wakeup of wq\n",
+ __func__);
+ goto err;
+ }
+ }
+ }
+ i2c_disable_irq(nfc_dev);
+
+ if (gpio_get_value(nfc_gpio->irq))
+ break;
+ if (!gpio_get_value(nfc_gpio->ven)) {
+ pr_info("%s: releasing read\n", __func__);
+ ret = -EIO;
+ goto err;
+ }
+ pr_warn("%s: spurious interrupt detected\n", __func__);
+ }
+ }
+
+ memset(buf, 0x00, count);
+ /* Read data */
+ ret = i2c_master_recv(i2c_dev->client, buf, count);
+ if (ret <= 0) {
+ pr_err("%s: returned %d\n", __func__, ret);
+ goto err;
+ }
+err:
+ return ret;
+}
+EXPORT_SYMBOL(i2c_read);
+
+int i2c_write(struct nfc_dev *nfc_dev, const char *buf, size_t count,
+ int max_retry_cnt)
+{
+ struct i2c_dev *i2c_dev = nfc_dev->platform_data;
+ struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio;
+ int ret = -EINVAL;
+ int retry_cnt;
+
+ if (!i2c_dev)
+ return -EINVAL;
+
+ if (count > MAX_DL_BUFFER_SIZE)
+ count = MAX_DL_BUFFER_SIZE;
+
+ pr_debug("%s: writing %zu bytes.\n", __func__, count);
+ /*
+ * Wait for any pending read for max 15ms before write
+ * This is to avoid any packet corruption during read, when
+ * the host cmds resets NFCC during any parallel read operation
+ */
+ for (retry_cnt = 1; retry_cnt <= MAX_WRITE_IRQ_COUNT; retry_cnt++) {
+ if (gpio_get_value(nfc_gpio->irq)) {
+ pr_warn("%s: irq high during write, wait\n", __func__);
+ usleep_range(NFC_WRITE_IRQ_WAIT_TIME_US,
+ NFC_WRITE_IRQ_WAIT_TIME_US + 100);
+ } else {
+ break;
+ }
+ if (retry_cnt == MAX_WRITE_IRQ_COUNT &&
+ gpio_get_value(nfc_gpio->irq)) {
+ pr_warn("%s: allow after maximum wait\n", __func__);
+ }
+ }
+
+ for (retry_cnt = 1; retry_cnt <= max_retry_cnt; retry_cnt++) {
+ ret = i2c_master_send(i2c_dev->client, buf, count);
+ if (ret <= 0) {
+ pr_warn("%s: write failed ret(%d), maybe in standby\n",
+ __func__, ret);
+ usleep_range(WRITE_RETRY_WAIT_TIME_US,
+ WRITE_RETRY_WAIT_TIME_US + 100);
+ } else if (ret != count) {
+ pr_err("%s: failed to write %d\n", __func__, ret);
+ ret = -EIO;
+ } else if (ret == count)
+ break;
+ }
+ return ret;
+}
+EXPORT_SYMBOL(i2c_write);
+
+ssize_t nfc_i2c_dev_read(struct file *filp, char __user *buf, size_t count,
+ loff_t *offset)
+{
+ int ret;
+ struct nfc_dev *nfc_dev = filp->private_data;
+
+ if (filp->f_flags & O_NONBLOCK) {
+ pr_err("%s: f_flags has nonblock. try again\n", __func__);
+ return -EAGAIN;
+ }
+ mutex_lock(&nfc_dev->read_mutex);
+ ret = i2c_read(nfc_dev, nfc_dev->read_kbuf, count, 0);
+ if (ret > 0) {
+ if (copy_to_user(buf, nfc_dev->read_kbuf, ret)) {
+ pr_warn("%s: failed to copy to user space\n", __func__);
+ ret = -EFAULT;
+ }
+ }
+ mutex_unlock(&nfc_dev->read_mutex);
+ return ret;
+}
+EXPORT_SYMBOL(nfc_i2c_dev_read);
+
+ssize_t nfc_i2c_dev_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *offset)
+{
+ int ret;
+ struct nfc_dev *nfc_dev = filp->private_data;
+
+ if (count > MAX_DL_BUFFER_SIZE)
+ count = MAX_DL_BUFFER_SIZE;
+
+ mutex_lock(&nfc_dev->write_mutex);
+ if (copy_from_user(nfc_dev->write_kbuf, buf, count)) {
+ pr_err("%s: failed to copy from user space\n", __func__);
+ mutex_unlock(&nfc_dev->write_mutex);
+ return -EFAULT;
+ }
+ ret = i2c_write(nfc_dev, nfc_dev->write_kbuf, count, NO_RETRY);
+ mutex_unlock(&nfc_dev->write_mutex);
+ return ret;
+}
+EXPORT_SYMBOL(nfc_i2c_dev_write);
+
+static const struct file_operations nfc_i2c_dev_fops = {
+ .owner = THIS_MODULE,
+ .llseek = noop_llseek,
+ .read = nfc_i2c_dev_read,
+ .write = nfc_i2c_dev_write,
+ .open = nfc_dev_open,
+ .release = nfc_dev_close,
+ .unlocked_ioctl = nfc_dev_ioctl,
+};
+
+int nfc_i2c_dev_probe(struct i2c_client *client)
+{
+ int ret = 0;
+ struct nfc_dev *nfc_dev = NULL;
+ struct i2c_dev *i2c_dev = NULL;
+ struct platform_configs nfc_configs;
+ struct platform_gpio *nfc_gpio = &nfc_configs.gpio;
+
+ pr_debug("%s: enter\n", __func__);
+ /* retrieve details of gpios from dt */
+ ret = nfc_parse_dt(&client->dev, &nfc_configs, PLATFORM_IF_I2C);
+ if (ret) {
+ pr_err("%s: failed to parse dt\n", __func__);
+ goto err;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ pr_err("%s: need I2C_FUNC_I2C\n", __func__);
+ ret = -ENODEV;
+ goto err;
+ }
+
+ /* 分配 nfc_dev 和 i2c_dev */
+ nfc_dev = kzalloc(sizeof(struct nfc_dev), GFP_KERNEL);
+ if (nfc_dev == NULL) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ i2c_dev = kzalloc(sizeof(struct i2c_dev), GFP_KERNEL);
+ if (i2c_dev == NULL) {
+ ret = -ENOMEM;
+ goto err_free_nfc_dev;
+ }
+
+ i2c_dev->client = client;
+ nfc_dev->platform_data = i2c_dev;
+ nfc_dev->interface = PLATFORM_IF_I2C;
+ nfc_dev->nfc_state = NFC_STATE_NCI;
+
+ nfc_dev->read_kbuf = kzalloc(MAX_NCI_BUFFER_SIZE, GFP_DMA | GFP_KERNEL);
+ if (!nfc_dev->read_kbuf) {
+ ret = -ENOMEM;
+ goto err_free_i2c_dev;
+ }
+
+ nfc_dev->write_kbuf = kzalloc(MAX_DL_BUFFER_SIZE, GFP_DMA | GFP_KERNEL);
+ if (!nfc_dev->write_kbuf) {
+ ret = -ENOMEM;
+ goto err_free_read_kbuf;
+ }
+
+ nfc_dev->nfc_read = i2c_read;
+ nfc_dev->nfc_write = i2c_write;
+ nfc_dev->nfc_enable_intr = i2c_enable_irq;
+ nfc_dev->nfc_disable_intr = i2c_disable_irq;
+
+ ret = configure_gpio(nfc_gpio->ven, GPIO_OUTPUT);
+ if (ret) {
+ pr_err("%s: unable to request nfc reset gpio [%d]\n", __func__,
+ nfc_gpio->ven);
+ goto err_free_write_kbuf;
+ }
+
+ ret = configure_gpio(nfc_gpio->irq, GPIO_IRQ);
+ if (ret <= 0) {
+ pr_err("%s: unable to request nfc irq gpio [%d]\n", __func__,
+ nfc_gpio->irq);
+ goto err_free_gpio;
+ }
+
+ client->irq = ret;
+ ret = configure_gpio(nfc_gpio->dwl_req, GPIO_OUTPUT);
+ if (ret) {
+ pr_err("%s: unable to request nfc firm downl gpio [%d]\n",
+ __func__, nfc_gpio->dwl_req);
+ }
+
+ /* copy the retrieved gpio details from DT */
+ memcpy(&nfc_dev->configs, &nfc_configs,
+ sizeof(struct platform_configs));
+
+ /* init mutex and queues */
+ init_waitqueue_head(&nfc_dev->read_wq);
+ mutex_init(&nfc_dev->read_mutex);
+ mutex_init(&nfc_dev->write_mutex);
+ mutex_init(&nfc_dev->dev_ref_mutex);
+ spin_lock_init(&i2c_dev->irq_enabled_lock);
+
+ ret = nfc_misc_register(nfc_dev, &nfc_i2c_dev_fops, DEV_COUNT,
+ NFC_CHAR_DEV_NAME, CLASS_NAME);
+ if (ret) {
+ pr_err("%s: nfc_misc_register failed\n", __func__);
+ goto err_mutex_destroy;
+ }
+
+ /* interrupt initializations */
+ pr_info("%s: requesting IRQ %d\n", __func__, client->irq);
+ i2c_dev->irq_enabled = true;
+ ret = request_irq(client->irq, i2c_irq_handler, IRQF_TRIGGER_HIGH,
+ client->name, nfc_dev);
+ if (ret) {
+ pr_err("%s: request_irq failed\n", __func__);
+ goto err_nfc_misc_unregister;
+ }
+
+ i2c_disable_irq(nfc_dev);
+ gpio_set_ven(nfc_dev, 1);
+ gpio_set_ven(nfc_dev, 0);
+ gpio_set_ven(nfc_dev, 1);
+ device_init_wakeup(&client->dev, true);
+ i2c_set_clientdata(client, nfc_dev);
+ i2c_dev->irq_wake_up = false;
+
+ pr_info("%s: probing nfc i2c successfully\n", __func__);
+ return 0;
+
+err_nfc_misc_unregister:
+ nfc_misc_unregister(nfc_dev, DEV_COUNT);
+err_mutex_destroy:
+ mutex_destroy(&nfc_dev->dev_ref_mutex);
+ mutex_destroy(&nfc_dev->read_mutex);
+ mutex_destroy(&nfc_dev->write_mutex);
+err_free_gpio:
+ gpio_free_all(nfc_dev);
+err_free_write_kbuf:
+ kfree(nfc_dev->write_kbuf);
+err_free_read_kbuf:
+ kfree(nfc_dev->read_kbuf);
+err_free_i2c_dev:
+ kfree(i2c_dev);
+err_free_nfc_dev:
+ kfree(nfc_dev);
+err:
+ pr_err("%s: probing not successful, check hardware\n", __func__);
+ return ret;
+}
+EXPORT_SYMBOL(nfc_i2c_dev_probe);
+
+void nfc_i2c_dev_remove(struct i2c_client *client)
+{
+ struct nfc_dev *nfc_dev = i2c_get_clientdata(client);
+ struct i2c_dev *i2c_dev;
+
+ if (!nfc_dev) {
+ pr_err("%s: device doesn't exist anymore\n", __func__);
+ return;
+ }
+
+ i2c_dev = nfc_dev->platform_data;
+
+ pr_info("%s: remove device\n", __func__);
+
+ if (nfc_dev->dev_ref_count > 0) {
+ pr_err("%s: device already in use\n", __func__);
+ return;
+ }
+
+ device_init_wakeup(&client->dev, false);
+ free_irq(client->irq, nfc_dev);
+ nfc_misc_unregister(nfc_dev, DEV_COUNT);
+ mutex_destroy(&nfc_dev->read_mutex);
+ mutex_destroy(&nfc_dev->write_mutex);
+ mutex_destroy(&nfc_dev->dev_ref_mutex);
+ gpio_free_all(nfc_dev);
+ kfree(nfc_dev->read_kbuf);
+ kfree(nfc_dev->write_kbuf);
+ kfree(i2c_dev);
+ kfree(nfc_dev);
+}
+EXPORT_SYMBOL(nfc_i2c_dev_remove);
+
+int nfc_i2c_dev_suspend(struct device *device)
+{
+ struct i2c_client *client = to_i2c_client(device);
+ struct nfc_dev *nfc_dev = i2c_get_clientdata(client);
+ struct i2c_dev *i2c_dev;
+
+ if (!nfc_dev)
+ return 0;
+
+ i2c_dev = nfc_dev->platform_data;
+ if (!i2c_dev)
+ return 0;
+
+ if (device_may_wakeup(&client->dev) && i2c_dev->irq_enabled) {
+ if (!enable_irq_wake(client->irq))
+ i2c_dev->irq_wake_up = true;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(nfc_i2c_dev_suspend);
+
+int nfc_i2c_dev_resume(struct device *device)
+{
+ struct i2c_client *client = to_i2c_client(device);
+ struct nfc_dev *nfc_dev = i2c_get_clientdata(client);
+ struct i2c_dev *i2c_dev;
+
+ if (!nfc_dev)
+ return 0;
+
+ i2c_dev = nfc_dev->platform_data;
+ if (!i2c_dev)
+ return 0;
+
+ if (device_may_wakeup(&client->dev) && i2c_dev->irq_wake_up) {
+ if (!disable_irq_wake(client->irq))
+ i2c_dev->irq_wake_up = false;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(nfc_i2c_dev_resume);
+
+static const struct i2c_device_id nfc_i2c_dev_id[] = {
+ { "nxpnfc", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, nfc_i2c_dev_id);
+
+static const struct of_device_id nfc_i2c_dev_match_table[] = {
+ { .compatible = "nxp,nxpnfc" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, nfc_i2c_dev_match_table);
+
+static const struct dev_pm_ops nfc_i2c_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(nfc_i2c_dev_suspend, nfc_i2c_dev_resume)
+};
+
+static struct i2c_driver nfc_i2c_dev_driver = {
+ .id_table = nfc_i2c_dev_id,
+ .probe = nfc_i2c_dev_probe,
+ .remove = nfc_i2c_dev_remove,
+ .driver = {
+ .name = "nxpnfc",
+ .pm = &nfc_i2c_dev_pm_ops,
+ .of_match_table = nfc_i2c_dev_match_table,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+
+static int __init nfc_i2c_dev_init(void)
+{
+ int ret = 0;
+
+ pr_info("%s: Loading NXP NFC I2C driver\n", __func__);
+ ret = i2c_add_driver(&nfc_i2c_dev_driver);
+ if (ret != 0)
+ pr_err("%s: NFC I2C add driver error ret %d\n", __func__, ret);
+ return ret;
+}
+
+module_init(nfc_i2c_dev_init);
+
+static void __exit nfc_i2c_dev_exit(void)
+{
+ pr_info("%s: Unloading NXP NFC I2C driver\n", __func__);
+ i2c_del_driver(&nfc_i2c_dev_driver);
+}
+
+module_exit(nfc_i2c_dev_exit);
+
+MODULE_DESCRIPTION("NXP NFC I2C driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/nfc/pn7160/i2c_drv.h b/drivers/nfc/pn7160/i2c_drv.h
new file mode 100644
index 000000000000..70f0faa0dd17
--- /dev/null
+++ b/drivers/nfc/pn7160/i2c_drv.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2019-2021 NXP
+ */
+
+#ifndef _I2C_DRV_H_
+#define _I2C_DRV_H_
+
+#include "platform.h"
+
+/* kept same as dts */
+#define NFC_I2C_DRV_STR "nxp,nxpnfc"
+#define NFC_I2C_DEV_ID "nxpnfc"
+
+/* Function declarations */
+ssize_t nfc_i2c_dev_read(struct file *filp, char __user *buf, size_t count,
+ loff_t *offset);
+ssize_t nfc_i2c_dev_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *offset);
+int nfc_i2c_dev_probe(struct i2c_client *client);
+void nfc_i2c_dev_remove(struct i2c_client *client);
+int nfc_i2c_dev_suspend(struct device *device);
+int nfc_i2c_dev_resume(struct device *device);
+
+#endif /* _I2C_DRV_H_ */
diff --git a/drivers/nfc/pn7160/platform.h b/drivers/nfc/pn7160/platform.h
new file mode 100644
index 000000000000..05468fa31193
--- /dev/null
+++ b/drivers/nfc/pn7160/platform.h
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2019-2021 NXP
+ */
+
+#ifndef _PLATFORM_H_
+#define _PLATFORM_H_
+
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/spinlock.h>
+#include <linux/miscdevice.h>
+
+/* nfc platform interface type */
+enum interface_flags {
+ /* I2C physical IF for NFCC */
+ PLATFORM_IF_I2C = 0,
+ PLATFORM_IF_SPI = 1,
+};
+
+/* nfc state flags */
+enum nfc_state_flags {
+ /* nfc in unknown state */
+ NFC_STATE_UNKNOWN = 0,
+ /* nfc in download mode */
+ NFC_STATE_FW_DWL = 0x1,
+ /* nfc booted in NCI mode */
+ NFC_STATE_NCI = 0x2,
+ /* nfc booted in Fw teared mode */
+ NFC_STATE_FW_TEARED = 0x4,
+};
+
+/*
+ * Power state for IBI handing, mainly needed to defer the IBI handling
+ * for the IBI received in suspend state to do it later in resume call
+ */
+enum pm_state_flags {
+ PM_STATE_NORMAL = 0,
+ PM_STATE_SUSPEND,
+ PM_STATE_IBI_BEFORE_RESUME,
+};
+
+/* Enum for GPIO values */
+enum gpio_values {
+ GPIO_INPUT = 0x0,
+ GPIO_OUTPUT = 0x1,
+ GPIO_HIGH = 0x2,
+ GPIO_OUTPUT_HIGH = 0x3,
+ GPIO_IRQ = 0x4,
+};
+
+/* Enum for nfcc_ioctl_request */
+enum nfcc_ioctl_request {
+ /* NFC disable request with VEN LOW */
+ NFC_POWER_OFF = 0,
+ /* NFC enable request with VEN Toggle */
+ NFC_POWER_ON,
+ /* firmware download request with VEN Toggle */
+ NFC_FW_DWL_VEN_TOGGLE,
+ /* ISO reset request */
+ NFC_ISO_RESET,
+ /* request for firmware download gpio HIGH */
+ NFC_FW_DWL_HIGH,
+ /* VEN hard reset request */
+ NFC_VEN_FORCED_HARD_RESET,
+ /* request for firmware download gpio LOW */
+ NFC_FW_DWL_LOW,
+};
+
+/* NFC GPIO variables */
+struct platform_gpio {
+ unsigned int irq;
+ unsigned int ven;
+ unsigned int dwl_req;
+};
+
+/* NFC Struct to get all the required configs from DTS */
+struct platform_configs {
+ struct platform_gpio gpio;
+};
+
+/* Interface specific parameters - 基础结构 */
+struct i2c_dev {
+ struct i2c_client *client;
+ /* IRQ parameters */
+ bool irq_enabled;
+ spinlock_t irq_enabled_lock;
+ /* NFC_IRQ wake-up state */
+ bool irq_wake_up;
+};
+
+struct spi_dev {
+ struct spi_device *client;
+ struct miscdevice device;
+ /* IRQ parameters */
+ bool irq_enabled;
+ spinlock_t irq_enabled_lock;
+ /* NFC_IRQ wake-up state */
+ bool irq_wake_up;
+ /* Temporary write kernel buffer */
+ uint8_t *tmp_write_kbuf;
+ /* Temporary read kernel buffer */
+ uint8_t *tmp_read_kbuf;
+};
+
+/* Device specific structure */
+struct nfc_dev {
+ wait_queue_head_t read_wq;
+ struct mutex read_mutex;
+ struct mutex write_mutex;
+ uint8_t *read_kbuf;
+ uint8_t *write_kbuf;
+ struct mutex dev_ref_mutex;
+ unsigned int dev_ref_count;
+ struct class *nfc_class;
+ struct device *nfc_device;
+ struct cdev c_dev;
+ dev_t devno;
+ /* Interface flag */
+ uint8_t interface;
+ /* nfc state flags */
+ uint8_t nfc_state;
+ /* NFC VEN pin state */
+ bool nfc_ven_enabled;
+ /* Platform specific data */
+ void *platform_data;
+ struct platform_configs configs;
+
+ /* function pointers */
+ int (*nfc_read)(struct nfc_dev *dev, char *buf, size_t count,
+ int timeout);
+ int (*nfc_write)(struct nfc_dev *dev, const char *buf,
+ const size_t count, int max_retry_cnt);
+ int (*nfc_enable_intr)(struct nfc_dev *dev);
+ int (*nfc_disable_intr)(struct nfc_dev *dev);
+};
+
+/* 从common.h移过来的常量定义 */
+#define DEV_COUNT 1
+#define CLASS_NAME "nfc"
+#define NFC_CHAR_DEV_NAME "nxpnfc"
+#define NCI_CMD (0x20)
+#define NCI_RSP (0x40)
+#define NCI_HDR_LEN (3)
+#define MAX_NCI_BUFFER_SIZE (NCI_HDR_LEN + 255)
+#define MAX_DL_BUFFER_SIZE (2 + 2 + 550)
+#define NO_RETRY (1)
+#define MAX_RETRY_COUNT (3)
+#define MAX_WRITE_IRQ_COUNT (5)
+#define WAKEUP_SRC_TIMEOUT (2000)
+#define NCI_CMD_RSP_TIMEOUT_MS (2000)
+#define NFC_GPIO_SET_WAIT_TIME_US (10000)
+#define NFC_WRITE_IRQ_WAIT_TIME_US (3000)
+#define WRITE_RETRY_WAIT_TIME_US (1000)
+#define NFC_MAGIC (0xE9)
+#define NFC_SET_PWR _IOW(NFC_MAGIC, 0x01, long)
+#define ESE_SET_PWR _IOW(NFC_MAGIC, 0x02, long)
+#define ESE_GET_PWR _IOR(NFC_MAGIC, 0x03, long)
+#define DTS_IRQ_GPIO_STR "nxp,nxpnfc-irq"
+#define DTS_VEN_GPIO_STR "nxp,nxpnfc-ven"
+#define DTS_FWDN_GPIO_STR "nxp,nxpnfc-fw-dwnld"
+
+#endif /* _PLATFORM_H_ */
--
2.25.1
Powered by blists - more mailing lists