diff mbox series

nfc: s3fwrn82: Add driver for Samsung S3FWRN82 NFC Chip

Message ID 20201112082047epcms2p3c164a73f89b1bdf2be97fe5e2c6936d2@epcms2p3 (mailing list archive)
State Superseded
Delegated to: Netdev Maintainers
Headers show
Series nfc: s3fwrn82: Add driver for Samsung S3FWRN82 NFC Chip | expand

Checks

Context Check Description
netdev/tree_selection success Not a local patch

Commit Message

Bongsu Jeon Nov. 12, 2020, 8:20 a.m. UTC
Add driver for Samsung S3FWRN82 NFC controller.
S3FWRN82 is using NCI protocol and I2C communication interface.

Signed-off-by: bongsujeon <bongsu.jeon@samsung.com>
---
 .../devicetree/bindings/net/nfc/s3fwrn82.txt  |  30 ++
 drivers/nfc/Kconfig                           |   1 +
 drivers/nfc/Makefile                          |   1 +
 drivers/nfc/s3fwrn82/Kconfig                  |  20 ++
 drivers/nfc/s3fwrn82/Makefile                 |  10 +
 drivers/nfc/s3fwrn82/core.c                   | 133 +++++++++
 drivers/nfc/s3fwrn82/i2c.c                    | 281 ++++++++++++++++++
 drivers/nfc/s3fwrn82/s3fwrn82.h               |  82 +++++
 8 files changed, 558 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/nfc/s3fwrn82.txt
 create mode 100644 drivers/nfc/s3fwrn82/Kconfig
 create mode 100644 drivers/nfc/s3fwrn82/Makefile
 create mode 100644 drivers/nfc/s3fwrn82/core.c
 create mode 100644 drivers/nfc/s3fwrn82/i2c.c
 create mode 100644 drivers/nfc/s3fwrn82/s3fwrn82.h

--

Comments

Jakub Kicinski Nov. 13, 2020, 1:18 a.m. UTC | #1
On Thu, 12 Nov 2020 17:20:47 +0900 Bongsu Jeon wrote:
> Add driver for Samsung S3FWRN82 NFC controller.
> S3FWRN82 is using NCI protocol and I2C communication interface.
> 
> Signed-off-by: bongsujeon <bongsu.jeon@samsung.com>

Please put [PATCH net-next] in the subject so we know this will go into
the net-next tree.

> diff --git a/Documentation/devicetree/bindings/net/nfc/s3fwrn82.txt b/Documentation/devicetree/bindings/net/nfc/s3fwrn82.txt
> new file mode 100644
> index 000000000000..03ed880e1c7f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/nfc/s3fwrn82.txt
> @@ -0,0 +1,30 @@
> +* Samsung S3FWRN82 NCI NFC Controller
> +
> +Required properties:
> +- compatible: Should be "samsung,s3fwrn82-i2c".
> +- reg: address on the bus
> +- interrupts: GPIO interrupt to which the chip is connected
> +- en-gpios: Output GPIO pin used for enabling/disabling the chip
> +- wake-gpios: Output GPIO pin used to enter firmware mode and
> +  sleep/wakeup control
> +
> +Example:
> +
> +    #include <dt-bindings/gpio/gpio.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +
> +    i2c4 {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        s3fwrn82@27 {
> +            compatible = "samsung,s3fwrn82-i2c";
> +            reg = <0x27>;
> +
> +            interrupt-parent = <&gpa1>;
> +            interrupts = <3 IRQ_TYPE_LEVEL_HIGH>;
> +
> +            en-gpios = <&gpf1 4 GPIO_ACTIVE_HIGH>;
> +            wake-gpios = <&gpj0 2 GPIO_ACTIVE_HIGH>;
> +        };
> +    };

AFAIK the device tree bindings need to be in a separate patch, and you
need to CC the device tree mailing list and maintainers on that patch.

> +config NFC_S3FWRN82
> +	tristate
> +	help
> +	  Core driver for Samsung S3FWRN82 NFC chip. Contains core utilities
> +	  of chip. It's intended to be used by PHYs to avoid duplicating lots
> +	  of common code.

If this is only selected by other drivers you can skip the help and
make this option invisible in kconfig.

> +config NFC_S3FWRN82_I2C
> +	tristate "Samsung S3FWRN82 I2C support"
> +	depends on NFC_NCI && I2C
> +	select NFC_S3FWRN82
> +	default n

default n is unnecessary, when default is not specified 'n' is already
the default

> +	help
> +	  This module adds support for an I2C interface to the S3FWRN82 chip.
> +	  Select this if your platform is using the I2C bus.
> +
> +	  To compile this driver as a module, choose m here. The module will
> +	  be called s3fwrn82_i2c.ko.
> +	  Say N if unsure.

> +#define S3FWRN82_NFC_PROTOCOLS  (NFC_PROTO_JEWEL_MASK | \
> +				NFC_PROTO_MIFARE_MASK | \
> +				NFC_PROTO_FELICA_MASK | \
> +				NFC_PROTO_ISO14443_MASK | \
> +				NFC_PROTO_ISO14443_B_MASK | \
> +				NFC_PROTO_ISO15693_MASK)
> +
> +static int s3fwrn82_nci_open(struct nci_dev *ndev)
> +{
> +	struct s3fwrn82_info *info = nci_get_drvdata(ndev);
> +
> +	if (s3fwrn82_get_mode(info) != S3FWRN82_MODE_COLD)
> +		return  -EBUSY;

double space


> +int s3fwrn82_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
> +	const struct s3fwrn82_phy_ops *phy_ops)

Please align the continuation lines properly. Please use checkpatch
--strict to check your patches.

> +{
> +	struct s3fwrn82_info *info;
> +	int ret;
> +
> +	info = devm_kzalloc(pdev, sizeof(*info), GFP_KERNEL);
> +	if (!info)
> +		return -ENOMEM;
> +
> +	info->phy_id = phy_id;
> +	info->pdev = pdev;
> +	info->phy_ops = phy_ops;
> +	mutex_init(&info->mutex);
> +
> +	s3fwrn82_set_mode(info, S3FWRN82_MODE_COLD);
> +
> +	info->ndev = nci_allocate_device(&s3fwrn82_nci_ops,
> +		S3FWRN82_NFC_PROTOCOLS, 0, 0);

same here

> +	if (!info->ndev)
> +		return -ENOMEM;
> +
> +	nci_set_parent_dev(info->ndev, pdev);
> +	nci_set_drvdata(info->ndev, info);
> +
> +	ret = nci_register_device(info->ndev);
> +	if (ret < 0) {
> +		nci_free_device(info->ndev);
> +		return ret;
> +	}
> +
> +	*ndev = info->ndev;
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(s3fwrn82_probe);

> +static int s3fwrn82_i2c_parse_dt(struct i2c_client *client)
> +{
> +	struct s3fwrn82_i2c_phy *phy = i2c_get_clientdata(client);
> +	struct device_node *np = client->dev.of_node;
> +
> +	if (!np)
> +		return -ENODEV;
> +
> +	phy->gpio_en = of_get_named_gpio(np, "en-gpios", 0);
> +	if (!gpio_is_valid(phy->gpio_en)) {
> +		return -ENODEV;
> +	}

brackets around single line statements are unnecessary

> +	phy->gpio_fw_wake = of_get_named_gpio(np, "wake-gpios", 0);
> +	if (!gpio_is_valid(phy->gpio_fw_wake)) {
> +		return -ENODEV;
> +	}

here as well

> +	return 0;
> +}

> +static inline int s3fwrn82_set_mode(struct s3fwrn82_info *info,
> +	enum s3fwrn82_mode mode)
> +{
> +	if (!info->phy_ops->set_mode)
> +		return -ENOTSUPP;

EOPNOTSUPP is a better error code, ENOTSUPP is internal to the kernel
and best avoided
diff mbox series

Patch

diff --git a/Documentation/devicetree/bindings/net/nfc/s3fwrn82.txt b/Documentation/devicetree/bindings/net/nfc/s3fwrn82.txt
new file mode 100644
index 000000000000..03ed880e1c7f
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/nfc/s3fwrn82.txt
@@ -0,0 +1,30 @@ 
+* Samsung S3FWRN82 NCI NFC Controller
+
+Required properties:
+- compatible: Should be "samsung,s3fwrn82-i2c".
+- reg: address on the bus
+- interrupts: GPIO interrupt to which the chip is connected
+- en-gpios: Output GPIO pin used for enabling/disabling the chip
+- wake-gpios: Output GPIO pin used to enter firmware mode and
+  sleep/wakeup control
+
+Example:
+
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c4 {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        s3fwrn82@27 {
+            compatible = "samsung,s3fwrn82-i2c";
+            reg = <0x27>;
+
+            interrupt-parent = <&gpa1>;
+            interrupts = <3 IRQ_TYPE_LEVEL_HIGH>;
+
+            en-gpios = <&gpf1 4 GPIO_ACTIVE_HIGH>;
+            wake-gpios = <&gpj0 2 GPIO_ACTIVE_HIGH>;
+        };
+    };
diff --git a/drivers/nfc/Kconfig b/drivers/nfc/Kconfig
index 75c65d339018..102654909d3a 100644
--- a/drivers/nfc/Kconfig
+++ b/drivers/nfc/Kconfig
@@ -59,4 +59,5 @@  source "drivers/nfc/st-nci/Kconfig"
 source "drivers/nfc/nxp-nci/Kconfig"
 source "drivers/nfc/s3fwrn5/Kconfig"
 source "drivers/nfc/st95hf/Kconfig"
+source "drivers/nfc/s3fwrn82/Kconfig"
 endmenu
diff --git a/drivers/nfc/Makefile b/drivers/nfc/Makefile
index 5393ba59b17d..518d83301ad2 100644
--- a/drivers/nfc/Makefile
+++ b/drivers/nfc/Makefile
@@ -17,3 +17,4 @@  obj-$(CONFIG_NFC_ST_NCI)	+= st-nci/
 obj-$(CONFIG_NFC_NXP_NCI)	+= nxp-nci/
 obj-$(CONFIG_NFC_S3FWRN5)	+= s3fwrn5/
 obj-$(CONFIG_NFC_ST95HF)	+= st95hf/
+obj-$(CONFIG_NFC_S3FWRN82)	+= s3fwrn82/
diff --git a/drivers/nfc/s3fwrn82/Kconfig b/drivers/nfc/s3fwrn82/Kconfig
new file mode 100644
index 000000000000..8765624c6fa4
--- /dev/null
+++ b/drivers/nfc/s3fwrn82/Kconfig
@@ -0,0 +1,20 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+config NFC_S3FWRN82
+	tristate
+	help
+	  Core driver for Samsung S3FWRN82 NFC chip. Contains core utilities
+	  of chip. It's intended to be used by PHYs to avoid duplicating lots
+	  of common code.
+
+config NFC_S3FWRN82_I2C
+	tristate "Samsung S3FWRN82 I2C support"
+	depends on NFC_NCI && I2C
+	select NFC_S3FWRN82
+	default n
+	help
+	  This module adds support for an I2C interface to the S3FWRN82 chip.
+	  Select this if your platform is using the I2C bus.
+
+	  To compile this driver as a module, choose m here. The module will
+	  be called s3fwrn82_i2c.ko.
+	  Say N if unsure.
diff --git a/drivers/nfc/s3fwrn82/Makefile b/drivers/nfc/s3fwrn82/Makefile
new file mode 100644
index 000000000000..198e2cd85e91
--- /dev/null
+++ b/drivers/nfc/s3fwrn82/Makefile
@@ -0,0 +1,10 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for Samsung S3FWRN82 NFC driver
+#
+
+s3fwrn82-objs = core.o
+s3fwrn82_i2c-objs = i2c.o
+
+obj-$(CONFIG_NFC_S3FWRN82) += s3fwrn82.o
+obj-$(CONFIG_NFC_S3FWRN82_I2C) += s3fwrn82_i2c.o
diff --git a/drivers/nfc/s3fwrn82/core.c b/drivers/nfc/s3fwrn82/core.c
new file mode 100644
index 000000000000..7ba60ec37fe3
--- /dev/null
+++ b/drivers/nfc/s3fwrn82/core.c
@@ -0,0 +1,133 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * NCI based driver for Samsung S3FWRN82 NFC chip
+ *
+ * Copyright (C) 2020 Samsung Electrnoics
+ * Bongsu Jeon <bongsu.jeon@samsung.com>
+ */
+
+#include <linux/module.h>
+#include <net/nfc/nci_core.h>
+
+#include "s3fwrn82.h"
+
+#define S3FWRN82_NFC_PROTOCOLS  (NFC_PROTO_JEWEL_MASK | \
+				NFC_PROTO_MIFARE_MASK | \
+				NFC_PROTO_FELICA_MASK | \
+				NFC_PROTO_ISO14443_MASK | \
+				NFC_PROTO_ISO14443_B_MASK | \
+				NFC_PROTO_ISO15693_MASK)
+
+static int s3fwrn82_nci_open(struct nci_dev *ndev)
+{
+	struct s3fwrn82_info *info = nci_get_drvdata(ndev);
+
+	if (s3fwrn82_get_mode(info) != S3FWRN82_MODE_COLD)
+		return  -EBUSY;
+
+	s3fwrn82_set_mode(info, S3FWRN82_MODE_NCI);
+	s3fwrn82_set_wake(info, true);
+
+	return 0;
+}
+
+static int s3fwrn82_nci_close(struct nci_dev *ndev)
+{
+	struct s3fwrn82_info *info = nci_get_drvdata(ndev);
+
+	s3fwrn82_set_wake(info, false);
+	s3fwrn82_set_mode(info, S3FWRN82_MODE_COLD);
+
+	return 0;
+}
+
+static int s3fwrn82_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
+{
+	struct s3fwrn82_info *info = nci_get_drvdata(ndev);
+	int ret;
+
+	mutex_lock(&info->mutex);
+
+	if (s3fwrn82_get_mode(info) != S3FWRN82_MODE_NCI) {
+		mutex_unlock(&info->mutex);
+		return -EINVAL;
+	}
+
+	ret = s3fwrn82_write(info, skb);
+	if (ret < 0)
+		kfree_skb(skb);
+
+	mutex_unlock(&info->mutex);
+	return ret;
+}
+
+static struct nci_ops s3fwrn82_nci_ops = {
+	.open = s3fwrn82_nci_open,
+	.close = s3fwrn82_nci_close,
+	.send = s3fwrn82_nci_send,
+};
+
+int s3fwrn82_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
+	const struct s3fwrn82_phy_ops *phy_ops)
+{
+	struct s3fwrn82_info *info;
+	int ret;
+
+	info = devm_kzalloc(pdev, sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	info->phy_id = phy_id;
+	info->pdev = pdev;
+	info->phy_ops = phy_ops;
+	mutex_init(&info->mutex);
+
+	s3fwrn82_set_mode(info, S3FWRN82_MODE_COLD);
+
+	info->ndev = nci_allocate_device(&s3fwrn82_nci_ops,
+		S3FWRN82_NFC_PROTOCOLS, 0, 0);
+	if (!info->ndev)
+		return -ENOMEM;
+
+	nci_set_parent_dev(info->ndev, pdev);
+	nci_set_drvdata(info->ndev, info);
+
+	ret = nci_register_device(info->ndev);
+	if (ret < 0) {
+		nci_free_device(info->ndev);
+		return ret;
+	}
+
+	*ndev = info->ndev;
+
+	return ret;
+}
+EXPORT_SYMBOL(s3fwrn82_probe);
+
+void s3fwrn82_remove(struct nci_dev *ndev)
+{
+	struct s3fwrn82_info *info = nci_get_drvdata(ndev);
+
+	s3fwrn82_set_mode(info, S3FWRN82_MODE_COLD);
+
+	nci_unregister_device(ndev);
+	nci_free_device(ndev);
+}
+EXPORT_SYMBOL(s3fwrn82_remove);
+
+int s3fwrn82_recv_frame(struct nci_dev *ndev, struct sk_buff *skb,
+	enum s3fwrn82_mode mode)
+{
+	switch (mode) {
+	case S3FWRN82_MODE_NCI:
+		return nci_recv_frame(ndev, skb);
+	default:
+		kfree_skb(skb);
+		return -ENODEV;
+	}
+}
+EXPORT_SYMBOL(s3fwrn82_recv_frame);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Samsung S3FWRN82 NFC driver");
+MODULE_AUTHOR("Bongsu Jeon <bongsu.jeon@samsung.com>");
diff --git a/drivers/nfc/s3fwrn82/i2c.c b/drivers/nfc/s3fwrn82/i2c.c
new file mode 100644
index 000000000000..26e60b76e6ca
--- /dev/null
+++ b/drivers/nfc/s3fwrn82/i2c.c
@@ -0,0 +1,281 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * I2C Link Layer for Samsung S3FWRN82 NCI based Driver
+ *
+ * Copyright (C) 2020 Samsung Electrnoics
+ * Bongsu Jeon <bongsu.jeon@samsung.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/module.h>
+
+#include <net/nfc/nfc.h>
+
+#include "s3fwrn82.h"
+
+#define S3FWRN82_I2C_DRIVER_NAME "s3fwrn82_i2c"
+
+#define S3FWRN82_EN_WAIT_TIME 20
+
+struct s3fwrn82_i2c_phy {
+	struct i2c_client *i2c_dev;
+	struct nci_dev *ndev;
+
+	unsigned int gpio_en;
+	unsigned int gpio_fw_wake;
+
+	struct mutex mutex;
+
+	enum s3fwrn82_mode mode;
+	unsigned int irq_skip:1;
+};
+
+static void s3fwrn82_i2c_set_wake(void *phy_id, bool wake)
+{
+	struct s3fwrn82_i2c_phy *phy = phy_id;
+
+	mutex_lock(&phy->mutex);
+	gpio_set_value(phy->gpio_fw_wake, wake);
+	if (wake == true)
+		msleep(S3FWRN82_EN_WAIT_TIME);
+	mutex_unlock(&phy->mutex);
+}
+
+static void s3fwrn82_i2c_set_mode(void *phy_id, enum s3fwrn82_mode mode)
+{
+	struct s3fwrn82_i2c_phy *phy = phy_id;
+
+	mutex_lock(&phy->mutex);
+
+	if (phy->mode == mode)
+		goto out;
+
+	phy->mode = mode;
+
+	gpio_set_value(phy->gpio_en, 1);
+	gpio_set_value(phy->gpio_fw_wake, 0);
+
+	if (mode != S3FWRN82_MODE_COLD) {
+		msleep(S3FWRN82_EN_WAIT_TIME);
+		gpio_set_value(phy->gpio_en, 0);
+		msleep(S3FWRN82_EN_WAIT_TIME/2);
+	}
+
+	phy->irq_skip = true;
+
+out:
+	mutex_unlock(&phy->mutex);
+}
+
+static enum s3fwrn82_mode s3fwrn82_i2c_get_mode(void *phy_id)
+{
+	struct s3fwrn82_i2c_phy *phy = phy_id;
+	enum s3fwrn82_mode mode;
+
+	mutex_lock(&phy->mutex);
+
+	mode = phy->mode;
+
+	mutex_unlock(&phy->mutex);
+
+	return mode;
+}
+
+static int s3fwrn82_i2c_write(void *phy_id, struct sk_buff *skb)
+{
+	struct s3fwrn82_i2c_phy *phy = phy_id;
+	int ret;
+
+	mutex_lock(&phy->mutex);
+
+	phy->irq_skip = false;
+
+	ret = i2c_master_send(phy->i2c_dev, skb->data, skb->len);
+	mutex_unlock(&phy->mutex);
+
+	if (ret < 0)
+		return ret;
+
+	if (ret != skb->len)
+		return -EREMOTEIO;
+
+	return 0;
+}
+
+static const struct s3fwrn82_phy_ops i2c_phy_ops = {
+	.set_wake = s3fwrn82_i2c_set_wake,
+	.set_mode = s3fwrn82_i2c_set_mode,
+	.get_mode = s3fwrn82_i2c_get_mode,
+	.write = s3fwrn82_i2c_write,
+};
+
+static int s3fwrn82_i2c_read(struct s3fwrn82_i2c_phy *phy)
+{
+	struct sk_buff *skb;
+	size_t hdr_size;
+	size_t data_len;
+	char hdr[4];
+	int ret;
+
+	hdr_size = NCI_CTRL_HDR_SIZE;
+	ret = i2c_master_recv(phy->i2c_dev, hdr, hdr_size);
+	if (ret < 0)
+		return ret;
+
+	if (ret < hdr_size)
+		return -EBADMSG;
+
+	data_len = ((struct nci_ctrl_hdr *)hdr)->plen;
+
+	skb = alloc_skb(hdr_size + data_len, GFP_KERNEL);
+	if (!skb)
+		return -ENOMEM;
+
+	skb_put_data(skb, hdr, hdr_size);
+
+	if (data_len == 0)
+		goto out;
+
+	ret = i2c_master_recv(phy->i2c_dev, skb_put(skb, data_len), data_len);
+	if (ret != data_len) {
+		kfree_skb(skb);
+		return -EBADMSG;
+	}
+
+out:
+	return s3fwrn82_recv_frame(phy->ndev, skb, phy->mode);
+}
+
+static irqreturn_t s3fwrn82_i2c_irq_thread_fn(int irq, void *phy_id)
+{
+	struct s3fwrn82_i2c_phy *phy = phy_id;
+
+	if (!phy || !phy->ndev) {
+		WARN_ON_ONCE(1);
+		return IRQ_NONE;
+	}
+
+	mutex_lock(&phy->mutex);
+
+	if (phy->irq_skip)
+		goto out;
+
+	switch (phy->mode) {
+	case S3FWRN82_MODE_NCI:
+		s3fwrn82_i2c_read(phy);
+		break;
+	case S3FWRN82_MODE_COLD:
+		break;
+	}
+
+out:
+	mutex_unlock(&phy->mutex);
+
+	return IRQ_HANDLED;
+}
+
+static int s3fwrn82_i2c_parse_dt(struct i2c_client *client)
+{
+	struct s3fwrn82_i2c_phy *phy = i2c_get_clientdata(client);
+	struct device_node *np = client->dev.of_node;
+
+	if (!np)
+		return -ENODEV;
+
+	phy->gpio_en = of_get_named_gpio(np, "en-gpios", 0);
+	if (!gpio_is_valid(phy->gpio_en)) {
+		return -ENODEV;
+	}
+
+	phy->gpio_fw_wake = of_get_named_gpio(np, "wake-gpios", 0);
+	if (!gpio_is_valid(phy->gpio_fw_wake)) {
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int s3fwrn82_i2c_probe(struct i2c_client *client,
+				  const struct i2c_device_id *id)
+{
+	struct s3fwrn82_i2c_phy *phy;
+	int ret;
+
+	phy = devm_kzalloc(&client->dev, sizeof(*phy), GFP_KERNEL);
+	if (!phy)
+		return -ENOMEM;
+
+	mutex_init(&phy->mutex);
+	phy->mode = S3FWRN82_MODE_COLD;
+	phy->irq_skip = true;
+
+	phy->i2c_dev = client;
+	i2c_set_clientdata(client, phy);
+
+	ret = s3fwrn82_i2c_parse_dt(client);
+	if (ret < 0)
+		return ret;
+
+	ret = devm_gpio_request_one(&phy->i2c_dev->dev, phy->gpio_en,
+		GPIOF_OUT_INIT_HIGH, "s3fwrn82_en");
+	if (ret < 0)
+		return ret;
+
+	ret = devm_gpio_request_one(&phy->i2c_dev->dev, phy->gpio_fw_wake,
+		GPIOF_OUT_INIT_LOW, "s3fwrn82_fw_wake");
+	if (ret < 0)
+		return ret;
+
+	ret = s3fwrn82_probe(&phy->ndev, phy, &phy->i2c_dev->dev, &i2c_phy_ops);
+	if (ret < 0)
+		return ret;
+
+	ret = devm_request_threaded_irq(&client->dev, phy->i2c_dev->irq, NULL,
+		s3fwrn82_i2c_irq_thread_fn, IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+		S3FWRN82_I2C_DRIVER_NAME, phy);
+	if (ret)
+		s3fwrn82_remove(phy->ndev);
+
+	return ret;
+}
+
+static int s3fwrn82_i2c_remove(struct i2c_client *client)
+{
+	struct s3fwrn82_i2c_phy *phy = i2c_get_clientdata(client);
+
+	s3fwrn82_remove(phy->ndev);
+
+	return 0;
+}
+
+static const struct i2c_device_id s3fwrn82_i2c_id_table[] = {
+	{S3FWRN82_I2C_DRIVER_NAME, 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, s3fwrn82_i2c_id_table);
+
+static const struct of_device_id of_s3fwrn82_i2c_match[] = {
+	{ .compatible = "samsung,s3fwrn82-i2c", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, of_s3fwrn82_i2c_match);
+
+static struct i2c_driver s3fwrn82_i2c_driver = {
+	.driver = {
+		.name = S3FWRN82_I2C_DRIVER_NAME,
+		.of_match_table = of_match_ptr(of_s3fwrn82_i2c_match),
+	},
+	.probe = s3fwrn82_i2c_probe,
+	.remove = s3fwrn82_i2c_remove,
+	.id_table = s3fwrn82_i2c_id_table,
+};
+
+module_i2c_driver(s3fwrn82_i2c_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("I2C driver for Samsung S3FWRN82");
+MODULE_AUTHOR("Bongsu Jeon <bongsu.jeon@samsung.com>");
diff --git a/drivers/nfc/s3fwrn82/s3fwrn82.h b/drivers/nfc/s3fwrn82/s3fwrn82.h
new file mode 100644
index 000000000000..5be6e08e04e2
--- /dev/null
+++ b/drivers/nfc/s3fwrn82/s3fwrn82.h
@@ -0,0 +1,82 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * NCI based driver for Samsung S3FWRN82 NFC chip
+ *
+ * Copyright (C) 2020 Samsung Electrnoics
+ * Bongsu Jeon <bongsu.jeon@samsung.com>
+ */
+
+#ifndef __LOCAL_S3FWRN82_H_
+#define __LOCAL_S3FWRN82_H_
+
+#include <linux/nfc.h>
+
+#include <net/nfc/nci_core.h>
+
+enum s3fwrn82_mode {
+	S3FWRN82_MODE_COLD,
+	S3FWRN82_MODE_NCI,
+};
+
+struct s3fwrn82_phy_ops {
+	void (*set_wake)(void *id, bool sleep);
+	void (*set_mode)(void *id, enum s3fwrn82_mode);
+	enum s3fwrn82_mode (*get_mode)(void *id);
+	int (*write)(void *id, struct sk_buff *skb);
+};
+
+struct s3fwrn82_info {
+	struct nci_dev *ndev;
+	void *phy_id;
+	struct device *pdev;
+
+	const struct s3fwrn82_phy_ops *phy_ops;
+
+	struct mutex mutex;
+};
+
+static inline int s3fwrn82_set_mode(struct s3fwrn82_info *info,
+	enum s3fwrn82_mode mode)
+{
+	if (!info->phy_ops->set_mode)
+		return -ENOTSUPP;
+
+	info->phy_ops->set_mode(info->phy_id, mode);
+
+	return 0;
+}
+
+static inline enum s3fwrn82_mode s3fwrn82_get_mode(struct s3fwrn82_info *info)
+{
+	if (!info->phy_ops->get_mode)
+		return -ENOTSUPP;
+
+	return info->phy_ops->get_mode(info->phy_id);
+}
+
+static inline int s3fwrn82_set_wake(struct s3fwrn82_info *info, bool wake)
+{
+	if (!info->phy_ops->set_wake)
+		return -ENOTSUPP;
+
+	info->phy_ops->set_wake(info->phy_id, wake);
+
+	return 0;
+}
+
+static inline int s3fwrn82_write(struct s3fwrn82_info *info, struct sk_buff *skb)
+{
+	if (!info->phy_ops->write)
+		return -ENOTSUPP;
+
+	return info->phy_ops->write(info->phy_id, skb);
+}
+
+int s3fwrn82_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
+	const struct s3fwrn82_phy_ops *phy_ops);
+void s3fwrn82_remove(struct nci_dev *ndev);
+
+int s3fwrn82_recv_frame(struct nci_dev *ndev, struct sk_buff *skb,
+	enum s3fwrn82_mode mode);
+
+#endif /* __LOCAL_S3FWRN82_H_ */