summaryrefslogtreecommitdiff
path: root/drivers/nfc
diff options
context:
space:
mode:
authorRobert Baldyga <r.baldyga@samsung.com>2015-04-09 15:09:58 +0200
committerSeung-Woo Kim <sw0312.kim@samsung.com>2016-12-14 13:44:49 +0900
commitc29f3ce4afddda1f62aa28b21483b5cc9e4bafa8 (patch)
treeb5636687879fff91f62f6d06d1676f73dd3e6667 /drivers/nfc
parentd4d0c4420c245d99f6e496a37b68ea5c8c950853 (diff)
LOCAL / nfc: add sec_nfc driver
Driver ported from Android 5.0 kernel. Signed-off-by: Robert Baldyga <r.baldyga@samsung.com> [k.kozlowski: rebased on 4.1] Signed-off-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
Diffstat (limited to 'drivers/nfc')
-rw-r--r--drivers/nfc/Kconfig49
-rw-r--r--drivers/nfc/Makefile1
-rw-r--r--drivers/nfc/sec_nfc.c992
3 files changed, 1042 insertions, 0 deletions
diff --git a/drivers/nfc/Kconfig b/drivers/nfc/Kconfig
index 107714e4405f..6b8b2a0abdc5 100644
--- a/drivers/nfc/Kconfig
+++ b/drivers/nfc/Kconfig
@@ -68,6 +68,55 @@ config NFC_PORT100
If unsure, say N.
+config SEC_NFC
+ bool "Samsung NFC driver"
+ default n
+ help
+ SAMSUNG NFC Driver.
+ This driver provides support for SAMSUNG NFC products.
+ You should select your NFC product and interface.
+
+ Say Y here to compile support for SAMSUNG NFC driver into the
+ kernel.
+
+choice
+ prompt "SEC NFC Products"
+ depends on SEC_NFC
+ default SEC_NFC_PRODUCT_N5
+config SEC_NFC_PRODUCT_N3
+ bool "N3"
+config SEC_NFC_PRODUCT_N5
+ bool "N5"
+endchoice
+
+choice
+ prompt "SEC NFC Interface"
+ depends on SEC_NFC
+ default SEC_NFC_IF_I2C
+config SEC_NFC_IF_UART
+ bool "UART"
+config SEC_NFC_IF_I2C
+ bool "I2C"
+config SEC_NFC_IF_I2C_GPIO
+ bool "I2C-GPIO (S/W-I2C)"
+ select I2C_ALGOBIT
+endchoice
+
+config SEC_NFC_CLK_REQ
+ bool "SEC NFC CLK REQ"
+ depends on SEC_NFC
+ default n
+ help
+ SAMSUNG NFC CLR REQ feature.
+
+config SEC_NFC_MARGINTIME
+ bool "SEC NFC MARGINTIME"
+ depends on SEC_NFC
+ default n
+ help
+ SAMSUNG SEC NFC MARGINTIME feature.
+ You should select this feature if your NFC product uses AVDD Cap 10uF.
+
source "drivers/nfc/pn544/Kconfig"
source "drivers/nfc/microread/Kconfig"
source "drivers/nfc/nfcmrvl/Kconfig"
diff --git a/drivers/nfc/Makefile b/drivers/nfc/Makefile
index a4292d790f9b..50def33ddba0 100644
--- a/drivers/nfc/Makefile
+++ b/drivers/nfc/Makefile
@@ -14,5 +14,6 @@ obj-$(CONFIG_NFC_TRF7970A) += trf7970a.o
obj-$(CONFIG_NFC_ST21NFCA) += st21nfca/
obj-$(CONFIG_NFC_ST21NFCB) += st21nfcb/
obj-$(CONFIG_NFC_NXP_NCI) += nxp-nci/
+obj-$(CONFIG_SEC_NFC) += sec_nfc.o
ccflags-$(CONFIG_NFC_DEBUG) := -DDEBUG
diff --git a/drivers/nfc/sec_nfc.c b/drivers/nfc/sec_nfc.c
new file mode 100644
index 000000000000..3053b583dc85
--- /dev/null
+++ b/drivers/nfc/sec_nfc.c
@@ -0,0 +1,992 @@
+/*
+ * SAMSUNG NFC Controller
+ *
+ * Copyright (C) 2013 Samsung Electronics Co.Ltd
+ * Author: Woonki Lee <woonki84.lee@samsung.com>
+ * Heejae Kim <heejae12.kim@samsung.com>
+ *
+ * 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
+ *
+ * Last update: 2014-07-15
+ *
+ */
+#ifdef CONFIG_SEC_NFC_IF_I2C_GPIO
+#define CONFIG_SEC_NFC_IF_I2C
+#endif
+
+#include <linux/wait.h>
+#include <linux/delay.h>
+
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <asm/uaccess.h>
+#include <linux/nfc/sec_nfc.h>
+#ifdef CONFIG_SEC_NFC_CLK_REQ
+#if defined(CONFIG_SOC_EXYNOS5430) || defined(CONFIG_SOC_EXYNOS5433) || \
+ defined(CONFIG_SOC_EXYNOS7420)
+#include <linux/clk-provider.h>
+#endif
+#include <linux/interrupt.h>
+#endif
+#include <linux/of_gpio.h>
+#include <linux/clk.h>
+
+#if defined(CONFIG_SOC_EXYNOS5433)
+#include <mach/regs-clock-exynos5433.h>
+#elif defined(CONFIG_SOC_EXYNOS7420)
+#include <mach/regs-clock-exynos7420.h>
+#endif
+
+#ifndef CONFIG_SEC_NFC_IF_I2C
+struct sec_nfc_i2c_info {};
+#define sec_nfc_read NULL
+#define sec_nfc_write NULL
+#define sec_nfc_poll NULL
+#define sec_nfc_i2c_irq_clear(x)
+
+#define SEC_NFC_GET_INFO(dev) platform_get_drvdata(to_platform_device(dev))
+
+#else /* CONFIG_SEC_NFC_IF_I2C */
+#include <linux/interrupt.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/i2c.h>
+
+#define SEC_NFC_GET_INFO(dev) i2c_get_clientdata(to_i2c_client(dev))
+enum sec_nfc_irq {
+ SEC_NFC_NONE,
+ SEC_NFC_INT,
+ SEC_NFC_SKIP,
+};
+
+struct sec_nfc_i2c_info {
+ struct i2c_client *i2c_dev;
+ struct mutex read_mutex;
+ enum sec_nfc_irq read_irq;
+ wait_queue_head_t read_wait;
+ size_t buflen;
+ u8 *buf;
+};
+
+#endif
+
+struct sec_nfc_info {
+ struct miscdevice miscdev;
+ struct mutex mutex;
+ enum sec_nfc_mode mode;
+ struct device *dev;
+ struct sec_nfc_platform_data *pdata;
+ struct sec_nfc_i2c_info i2c_info;
+#ifdef CONFIG_SEC_NFC_CLK_REQ
+ bool clk_ctl;
+ bool clk_state;
+#if defined(CONFIG_SOC_EXYNOS5430) || defined(CONFIG_SOC_EXYNOS5433) || \
+ defined(CONFIG_SOC_EXYNOS7420)
+ struct platform_device *pdev;
+#endif
+#endif
+};
+
+#ifdef CONFIG_SEC_NFC_IF_I2C
+static irqreturn_t sec_nfc_irq_thread_fn(int irq, void *dev_id)
+{
+ struct sec_nfc_info *info = dev_id;
+ struct sec_nfc_platform_data *pdata = info->pdata;
+
+ dev_info(info->dev, "[NFC] Read Interrupt is occurred!\n");
+
+ if(gpio_get_value(pdata->irq) == 0) {
+ dev_err(info->dev, "[NFC] Warning,irq-gpio state is low!\n");
+ return IRQ_HANDLED;
+ }
+ mutex_lock(&info->i2c_info.read_mutex);
+ /* Skip interrupt during power switching
+ * It is released after first write */
+ if (info->i2c_info.read_irq == SEC_NFC_SKIP) {
+ dev_dbg(info->dev, "%s: Now power swiching. Skip this IRQ\n", __func__);
+ mutex_unlock(&info->i2c_info.read_mutex);
+ return IRQ_HANDLED;
+ }
+
+ info->i2c_info.read_irq = SEC_NFC_INT;
+ mutex_unlock(&info->i2c_info.read_mutex);
+
+ wake_up_interruptible(&info->i2c_info.read_wait);
+
+ return IRQ_HANDLED;
+}
+
+static ssize_t sec_nfc_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct sec_nfc_info *info = container_of(file->private_data,
+ struct sec_nfc_info, miscdev);
+ enum sec_nfc_irq irq;
+ int ret = 0;
+
+ dev_dbg(info->dev, "%s: info: %p, count: %zu\n", __func__,
+ info, count);
+
+ mutex_lock(&info->mutex);
+
+ if (info->mode == SEC_NFC_MODE_OFF) {
+ dev_err(info->dev, "sec_nfc is not enabled\n");
+ ret = -ENODEV;
+ goto out;
+ }
+
+ mutex_lock(&info->i2c_info.read_mutex);
+ irq = info->i2c_info.read_irq;
+ mutex_unlock(&info->i2c_info.read_mutex);
+ if (irq == SEC_NFC_NONE) {
+ if (file->f_flags & O_NONBLOCK) {
+ dev_err(info->dev, "it is nonblock\n");
+ ret = -EAGAIN;
+ goto out;
+ }
+ }
+
+ /* i2c recv */
+ if (count > info->i2c_info.buflen)
+ count = info->i2c_info.buflen;
+
+ if (count > SEC_NFC_MSG_MAX_SIZE) {
+ dev_err(info->dev, "user required wrong size :%d\n", (u32)count);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ mutex_lock(&info->i2c_info.read_mutex);
+ memset(info->i2c_info.buf, 0, count);
+ ret = i2c_master_recv(info->i2c_info.i2c_dev, info->i2c_info.buf, (u32)count);
+ dev_dbg(info->dev, "recv size : %d\n", ret);
+
+ if (ret == -EREMOTEIO) {
+ ret = -ERESTART;
+ goto read_error;
+ } else if (ret != count) {
+ dev_err(info->dev, "read failed: return: %d count: %d\n",
+ ret, (u32)count);
+ //ret = -EREMOTEIO;
+ goto read_error;
+ }
+
+ info->i2c_info.read_irq = SEC_NFC_NONE;
+ mutex_unlock(&info->i2c_info.read_mutex);
+
+ if (copy_to_user(buf, info->i2c_info.buf, ret)) {
+ dev_err(info->dev, "copy failed to user\n");
+ ret = -EFAULT;
+ }
+
+ goto out;
+
+read_error:
+ info->i2c_info.read_irq = SEC_NFC_NONE;
+ mutex_unlock(&info->i2c_info.read_mutex);
+out:
+ mutex_unlock(&info->mutex);
+
+ return ret;
+}
+
+static ssize_t sec_nfc_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct sec_nfc_info *info = container_of(file->private_data,
+ struct sec_nfc_info, miscdev);
+ int ret = 0;
+
+ dev_dbg(info->dev, "%s: info: %p, count %d\n", __func__,
+ info, (u32)count);
+
+ mutex_lock(&info->mutex);
+
+ if (info->mode == SEC_NFC_MODE_OFF) {
+ dev_err(info->dev, "sec_nfc is not enabled\n");
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (count > info->i2c_info.buflen)
+ count = info->i2c_info.buflen;
+
+ if (count > SEC_NFC_MSG_MAX_SIZE) {
+ dev_err(info->dev, "user required wrong size :%d\n", (u32)count);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (copy_from_user(info->i2c_info.buf, buf, count)) {
+ dev_err(info->dev, "copy failed from user\n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ /* Skip interrupt during power switching
+ * It is released after first write */
+ mutex_lock(&info->i2c_info.read_mutex);
+ ret = i2c_master_send(info->i2c_info.i2c_dev, info->i2c_info.buf, count);
+ if (info->i2c_info.read_irq == SEC_NFC_SKIP)
+ info->i2c_info.read_irq = SEC_NFC_NONE;
+ mutex_unlock(&info->i2c_info.read_mutex);
+
+ if (ret == -EREMOTEIO) {
+ dev_err(info->dev, "send failed: return: %d count: %d\n",
+ ret, (u32)count);
+ ret = -ERESTART;
+ goto out;
+ }
+
+ if (ret != count) {
+ dev_err(info->dev, "send failed: return: %d count: %d\n",
+ ret, (u32)count);
+ ret = -EREMOTEIO;
+ }
+
+out:
+ mutex_unlock(&info->mutex);
+
+ return ret;
+}
+
+static unsigned int sec_nfc_poll(struct file *file, poll_table *wait)
+{
+ struct sec_nfc_info *info = container_of(file->private_data,
+ struct sec_nfc_info, miscdev);
+ enum sec_nfc_irq irq;
+
+ int ret = 0;
+
+ dev_dbg(info->dev, "%s: info: %p\n", __func__, info);
+
+ mutex_lock(&info->mutex);
+
+ if (info->mode == SEC_NFC_MODE_OFF) {
+ dev_err(info->dev, "sec_nfc is not enabled\n");
+ ret = -ENODEV;
+ goto out;
+ }
+
+ poll_wait(file, &info->i2c_info.read_wait, wait);
+
+ mutex_lock(&info->i2c_info.read_mutex);
+ irq = info->i2c_info.read_irq;
+ if (irq == SEC_NFC_INT)
+ ret = (POLLIN | POLLRDNORM);
+ mutex_unlock(&info->i2c_info.read_mutex);
+
+out:
+ mutex_unlock(&info->mutex);
+
+ return ret;
+}
+
+void sec_nfc_i2c_irq_clear(struct sec_nfc_info *info)
+{
+ /* clear interrupt. Interrupt will be occured at power off */
+ mutex_lock(&info->i2c_info.read_mutex);
+ info->i2c_info.read_irq = SEC_NFC_NONE;
+ mutex_unlock(&info->i2c_info.read_mutex);
+}
+
+int sec_nfc_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct sec_nfc_info *info = dev_get_drvdata(dev);
+ struct sec_nfc_platform_data *pdata = info->pdata;
+ int ret;
+
+ dev_dbg(info->dev, "%s: start: %p\n", __func__, info);
+
+ info->i2c_info.buflen = SEC_NFC_MAX_BUFFER_SIZE;
+ info->i2c_info.buf = kzalloc(SEC_NFC_MAX_BUFFER_SIZE, GFP_KERNEL);
+ if (!info->i2c_info.buf) {
+ dev_err(dev,
+ "failed to allocate memory for sec_nfc_info->buf\n");
+ return -ENOMEM;
+ }
+ info->i2c_info.i2c_dev = client;
+ info->i2c_info.read_irq = SEC_NFC_NONE;
+ mutex_init(&info->i2c_info.read_mutex);
+ init_waitqueue_head(&info->i2c_info.read_wait);
+ i2c_set_clientdata(client, info);
+
+ ret = gpio_request(pdata->irq, "nfc_int");
+ if (ret) {
+ dev_err(dev, "GPIO request is failed to register IRQ\n");
+ goto err_irq_req;
+ }
+ gpio_direction_input(pdata->irq);
+
+ ret = request_threaded_irq(client->irq, NULL, sec_nfc_irq_thread_fn,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT, SEC_NFC_DRIVER_NAME,
+ info);
+ if (ret < 0) {
+ dev_err(dev, "failed to register IRQ handler\n");
+ kfree(info->i2c_info.buf);
+ return ret;
+ }
+
+ dev_dbg(info->dev, "%s: success: %p\n", __func__, info);
+ return 0;
+
+err_irq_req:
+ return ret;
+}
+
+void sec_nfc_i2c_remove(struct device *dev)
+{
+ struct sec_nfc_info *info = dev_get_drvdata(dev);
+ struct i2c_client *client = info->i2c_info.i2c_dev;
+ struct sec_nfc_platform_data *pdata = info->pdata;
+ free_irq(client->irq, info);
+ gpio_free(pdata->irq);
+}
+#endif /* CONFIG_SEC_NFC_IF_I2C */
+
+#ifdef CONFIG_SEC_NFC_CLK_REQ
+static irqreturn_t sec_nfc_clk_irq_thread(int irq, void *dev_id)
+{
+ struct sec_nfc_info *info = dev_id;
+ struct sec_nfc_platform_data *pdata = info->pdata;
+ bool value;
+
+ dev_dbg(info->dev, "[NFC]Clock Interrupt is occurred!\n");
+ value = gpio_get_value(pdata->clk_req) > 0 ? true : false;
+
+#if defined(CONFIG_SOC_EXYNOS5430) || defined(CONFIG_SOC_EXYNOS5433) || \
+ defined(CONFIG_SOC_EXYNOS7420)
+ if (value == info->clk_state)
+ return IRQ_HANDLED;
+ if (value)
+ {
+#ifdef CONFIG_SOC_EXYNOS5433
+ clk_prepare_enable(pdata->gate_top_cam1);
+#endif
+ clk_prepare_enable(pdata->clk);
+ }
+ else
+ {
+ clk_disable_unprepare(pdata->clk);
+#ifdef CONFIG_SOC_EXYNOS5433
+ clk_disable_unprepare(pdata->gate_top_cam1);
+#endif
+ }
+#else
+ value = gpio_get_value(pdata->clk_req) > 0 ? 1 : 0;
+ gpio_set_value(pdata->clk_req, value);
+#endif
+
+ info->clk_state = value;
+
+ return IRQ_HANDLED;
+}
+
+void sec_nfc_clk_ctl_enable(struct sec_nfc_info *info)
+{
+ struct sec_nfc_platform_data *pdata = info->pdata;
+ unsigned int irq = gpio_to_irq(pdata->clk_req);
+ int ret;
+
+ if (info->clk_ctl)
+ return;
+
+ info->clk_state = false;
+ ret = request_threaded_irq(irq, NULL, sec_nfc_clk_irq_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ SEC_NFC_DRIVER_NAME, info);
+ if (ret < 0) {
+ dev_err(info->dev, "failed to register CLK REQ IRQ handler\n");
+ }
+ info->clk_ctl = true;
+}
+void sec_nfc_clk_ctl_disable(struct sec_nfc_info *info)
+{
+ struct sec_nfc_platform_data *pdata = info->pdata;
+ unsigned int irq = gpio_to_irq(pdata->clk_req);
+
+ if (!info->clk_ctl)
+ return;
+
+ free_irq(irq, info);
+ if (info->clk_state)
+ {
+ clk_disable_unprepare(pdata->clk);
+#ifdef CONFIG_SOC_EXYNOS5433
+ clk_disable_unprepare(pdata->gate_top_cam1);
+#endif
+ }
+ info->clk_state = false;
+ info->clk_ctl = false;
+}
+#else
+#define sec_nfc_clk_ctl_enable(x)
+#define sec_nfc_clk_ctl_disable(x)
+#endif /* CONFIG_SEC_NFC_CLK_REQ */
+
+static void sec_nfc_set_mode(struct sec_nfc_info *info,
+ enum sec_nfc_mode mode)
+{
+ struct sec_nfc_platform_data *pdata = info->pdata;
+
+ /* intfo lock is aleady gotten before calling this function */
+ if (info->mode == mode) {
+ dev_dbg(info->dev, "Power mode is already %d", mode);
+ return;
+ }
+ info->mode = mode;
+
+#ifdef CONFIG_SEC_NFC_IF_I2C
+ /* Skip interrupt during power switching
+ * It is released after first write */
+ mutex_lock(&info->i2c_info.read_mutex);
+ info->i2c_info.read_irq = SEC_NFC_SKIP;
+ mutex_unlock(&info->i2c_info.read_mutex);
+#endif
+
+ gpio_set_value(pdata->ven, SEC_NFC_PW_OFF);
+ if (pdata->firm) gpio_set_value(pdata->firm, SEC_NFC_FW_OFF);
+
+ if (mode == SEC_NFC_MODE_BOOTLOADER)
+ if (pdata->firm) gpio_set_value(pdata->firm, SEC_NFC_FW_ON);
+
+ if (mode != SEC_NFC_MODE_OFF)
+ {
+ msleep(SEC_NFC_VEN_WAIT_TIME);
+ gpio_set_value(pdata->ven, SEC_NFC_PW_ON);
+ sec_nfc_clk_ctl_enable(info);
+#ifdef CONFIG_SEC_NFC_IF_I2C
+ enable_irq_wake(info->i2c_info.i2c_dev->irq);
+#endif
+ msleep(SEC_NFC_VEN_WAIT_TIME/2);
+ } else {
+ sec_nfc_clk_ctl_disable(info);
+#ifdef CONFIG_SEC_NFC_IF_I2C
+ disable_irq_wake(info->i2c_info.i2c_dev->irq);
+#endif
+ }
+
+ dev_dbg(info->dev, "Power mode is : %d\n", mode);
+}
+
+static long sec_nfc_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct sec_nfc_info *info = container_of(file->private_data,
+ struct sec_nfc_info, miscdev);
+ struct sec_nfc_platform_data *pdata = info->pdata;
+ unsigned int new = (unsigned int)arg;
+ int ret = 0;
+
+ dev_dbg(info->dev, "%s: info: %p, cmd: 0x%x\n",
+ __func__, info, cmd);
+
+ mutex_lock(&info->mutex);
+
+ switch (cmd) {
+ case SEC_NFC_GET_MODE:
+ put_user(info->mode, (int __user *)arg);
+ break;
+ case SEC_NFC_SET_MODE:
+ dev_dbg(info->dev, "%s: SEC_NFC_SET_MODE\n", __func__);
+
+ if (info->mode == new)
+ break;
+
+ if (new >= SEC_NFC_MODE_COUNT) {
+ dev_err(info->dev, "wrong mode (%d)\n", new);
+ ret = -EFAULT;
+ break;
+ }
+ sec_nfc_set_mode(info, new);
+
+ break;
+
+#if defined(CONFIG_SEC_NFC_PRODUCT_N3)
+ case SEC_NFC_SLEEP:
+ case SEC_NFC_WAKEUP:
+ break;
+
+#elif defined(CONFIG_SEC_NFC_PRODUCT_N5)
+ case SEC_NFC_SLEEP:
+ if (info->mode != SEC_NFC_MODE_BOOTLOADER)
+ gpio_set_value(pdata->wake, SEC_NFC_WAKE_SLEEP);
+ break;
+
+ case SEC_NFC_WAKEUP:
+ if (info->mode != SEC_NFC_MODE_BOOTLOADER)
+ gpio_set_value(pdata->wake, SEC_NFC_WAKE_UP);
+ break;
+#endif
+
+ default:
+ dev_err(info->dev, "Unknow ioctl 0x%x\n", cmd);
+ ret = -ENOIOCTLCMD;
+ break;
+ }
+
+ mutex_unlock(&info->mutex);
+
+ return ret;
+}
+
+static int sec_nfc_open(struct inode *inode, struct file *file)
+{
+ struct sec_nfc_info *info = container_of(file->private_data,
+ struct sec_nfc_info, miscdev);
+ int ret = 0;
+
+ dev_dbg(info->dev, "%s: info : %p" , __func__, info);
+
+ mutex_lock(&info->mutex);
+ if (info->mode != SEC_NFC_MODE_OFF) {
+ dev_err(info->dev, "sec_nfc is busy\n");
+ ret = -EBUSY;
+ goto out;
+ }
+
+ sec_nfc_set_mode(info, SEC_NFC_MODE_OFF);
+
+out:
+ mutex_unlock(&info->mutex);
+ return ret;
+}
+
+static int sec_nfc_close(struct inode *inode, struct file *file)
+{
+ struct sec_nfc_info *info = container_of(file->private_data,
+ struct sec_nfc_info, miscdev);
+
+ dev_dbg(info->dev, "%s: info : %p" , __func__, info);
+
+ mutex_lock(&info->mutex);
+ sec_nfc_set_mode(info, SEC_NFC_MODE_OFF);
+ mutex_unlock(&info->mutex);
+
+ return 0;
+}
+
+static const struct file_operations sec_nfc_fops = {
+ .owner = THIS_MODULE,
+ .read = sec_nfc_read,
+ .write = sec_nfc_write,
+ .poll = sec_nfc_poll,
+ .open = sec_nfc_open,
+ .release = sec_nfc_close,
+ .unlocked_ioctl = sec_nfc_ioctl,
+};
+
+#ifdef CONFIG_PM
+static int sec_nfc_suspend(struct device *dev)
+{
+ struct sec_nfc_info *info = SEC_NFC_GET_INFO(dev);
+ int ret = 0;
+
+ mutex_lock(&info->mutex);
+
+ if (info->mode == SEC_NFC_MODE_BOOTLOADER)
+ ret = -EPERM;
+
+ mutex_unlock(&info->mutex);
+
+ return ret;
+}
+
+static int sec_nfc_resume(struct device *dev)
+{
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(sec_nfc_pm_ops, sec_nfc_suspend, sec_nfc_resume);
+#endif
+
+#ifdef CONFIG_OF
+/*device tree parsing*/
+static int sec_nfc_parse_dt(struct device *dev,
+ struct sec_nfc_platform_data *pdata)
+{
+ struct device_node *np = dev->of_node;
+ pdata->ven = of_get_named_gpio(np, "sec-nfc,ven-gpio", 0);
+ pdata->firm = of_get_named_gpio(np, "sec-nfc,firm-gpio", 0);
+ pdata->wake = pdata->firm;
+#ifdef CONFIG_SEC_NFC_IF_I2C
+ pdata->irq = of_get_named_gpio(np, "sec-nfc,irq-gpio", 0);
+#endif
+#ifdef CONFIG_SEC_NFC_CLK_REQ
+ pdata->clk_req = of_get_named_gpio(np, "sec-nfc,clk_req-gpio", 0);
+#endif
+
+ pr_info("%s: irq : %d, ven : %d, firm : %d\n",
+ __func__, pdata->irq, pdata->ven, pdata->firm);
+ return 0;
+}
+
+#ifdef CONFIG_SEC_NFC_CLK_REQ
+#if defined(CONFIG_SOC_EXYNOS5430) || defined(CONFIG_SOC_EXYNOS5433) || \
+ defined(CONFIG_SOC_EXYNOS7420)
+
+
+/* utility function to set parent with DT */
+int sec_nfc_set_parent_dt(struct platform_device *pdev,
+ const char *child, const char *parent)
+{
+ int ret = 0;
+ struct clk *p;
+ struct clk *c;
+
+ p = clk_get(&pdev->dev, parent);
+ if (IS_ERR_OR_NULL(p)) {
+ pr_err("%s: could not lookup clock : %s\n", __func__, parent);
+ return -EINVAL;
+ }
+
+ c = clk_get(&pdev->dev, child);
+ if (IS_ERR_OR_NULL(c)) {
+ pr_err("%s: could not lookup clock : %s\n", __func__, child);
+ return -EINVAL;
+ }
+
+ ret = clk_set_parent(c, p);
+ if (ret) {
+ pr_err("%s: clk_set_parent is fail(%s -> %s)\n", __func__, child, parent);
+ return ret;
+ }
+
+ return 0;
+}
+
+
+/* utility function to set rate with DT */
+int sec_nfc_set_rate_dt(struct platform_device *pdev,
+ const char *conid, unsigned int rate)
+{
+ int ret = 0;
+ struct clk *target;
+
+ target = clk_get(&pdev->dev, conid);
+ if (IS_ERR_OR_NULL(target)) {
+ pr_err("%s: could not lookup clock : %s\n", __func__, conid);
+ return -EINVAL;
+ }
+
+ ret = clk_set_rate(target, rate);
+ if (ret) {
+ pr_err("%s: clk_set_rate is fail(%s)\n", __func__, conid);
+ return ret;
+ }
+
+ /* sec_nfc_get_rate_dt(pdev, conid); */
+
+ return 0;
+}
+
+/* utility function to get rate with DT */
+int sec_nfc_get_rate_dt(struct platform_device *pdev,
+ const char *conid)
+{
+ struct clk *target;
+ unsigned int rate_target;
+
+ target = clk_get(&pdev->dev, conid);
+ if (IS_ERR_OR_NULL(target)) {
+ pr_err("%s: could not lookup clock : %s\n", __func__, conid);
+ return -EINVAL;
+ }
+
+ rate_target = clk_get_rate(target);
+ pr_info("%s : %dMhz\n", conid, rate_target/1000000);
+
+ return rate_target;
+}
+
+static int sec_nfc_clk_parse_dt(struct sec_nfc_info *info)
+{
+ struct sec_nfc_platform_data *pdata = info->pdata;
+ u32 frequency;
+ int ret;
+
+ ret = sec_nfc_set_parent_dt(info->pdev, "mout_sclk_isp_sensor1", "oscclk");
+ if (ret) {
+ pr_err("%s, sec_nfc_set_parent_dt:%d\n", __func__, ret);
+ return -EPERM;
+ }
+ ret = sec_nfc_set_rate_dt(info->pdev, "dout_sclk_isp_sensor1_a", 24 * 1000000);
+ if (ret) {
+ pr_err("%s, sec_nfc_set_rate_dt A:%d\n", __func__, ret);
+ return -EPERM;
+ }
+ ret = sec_nfc_set_rate_dt(info->pdev, "dout_sclk_isp_sensor1_b", 24 * 1000000);
+ if (ret) {
+ pr_err("%s, sec_nfc_set_rate_dt B:%d\n", __func__, ret);
+ return -EPERM;
+ }
+ frequency = sec_nfc_get_rate_dt(info->pdev, "sclk_isp_sensor1");
+ pr_info("%s(mclk : %d)\n", __func__, frequency);
+
+ pdata->clk = clk_get(info->dev, "sclk_isp_sensor1");
+ if(IS_ERR(pdata->clk)){
+ pr_err("%s: clk not found\n",__func__);
+ return -EPERM;
+ }
+#ifdef CONFIG_SOC_EXYNOS5433
+ pdata->gate_top_cam1 = samsung_clk_get_by_reg((unsigned long)EXYNOS5430_ENABLE_IP_TOP,6);
+ if(IS_ERR(pdata->gate_top_cam1)){
+ pr_err("%s : cam1 clk not found\n", __func__);
+ return -ENODEV;
+ }
+#endif
+ return 0;
+}
+#endif
+#endif
+#else
+static int sec_nfc_parse_dt(struct device *dev,
+ struct sec_nfc_platform_data *pdata)
+{
+ return -ENODEV;
+}
+#endif
+static int __sec_nfc_probe(struct device *dev)
+{
+ struct sec_nfc_info *info;
+ struct sec_nfc_platform_data *pdata = NULL;
+ int ret = 0;
+
+ dev_dbg(dev, "[NFC]sec-nfc probe start \n");
+ if (dev->of_node) {
+ pdata = devm_kzalloc(dev,
+ sizeof(struct sec_nfc_platform_data), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(dev, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+ ret = sec_nfc_parse_dt(dev, pdata);
+ if (ret)
+ return ret;
+ } else {
+ pdata = dev->platform_data;
+ }
+
+ if (!pdata) {
+ dev_err(dev, "No platform data\n");
+ ret = -ENOMEM;
+ goto err_pdata;
+ }
+
+ info = kzalloc(sizeof(struct sec_nfc_info), GFP_KERNEL);
+ if (!info) {
+ dev_err(dev, "failed to allocate memory for sec_nfc_info\n");
+ ret = -ENOMEM;
+ goto err_info_alloc;
+ }
+ info->dev = dev;
+ info->pdata = pdata;
+ info->mode = SEC_NFC_MODE_OFF;
+
+ mutex_init(&info->mutex);
+ dev_set_drvdata(dev, info);
+
+ info->miscdev.minor = MISC_DYNAMIC_MINOR;
+ info->miscdev.name = SEC_NFC_DRIVER_NAME;
+ info->miscdev.fops = &sec_nfc_fops;
+ info->miscdev.parent = dev;
+ ret = misc_register(&info->miscdev);
+ if (ret < 0) {
+ dev_err(dev, "failed to register Device\n");
+ goto err_dev_reg;
+ }
+
+#ifdef CONFIG_SEC_NFC_CLK_REQ
+#if defined(CONFIG_SOC_EXYNOS5430) || defined(CONFIG_SOC_EXYNOS5433) || \
+ defined(CONFIG_SOC_EXYNOS7420)
+ info->pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL);
+ if (info->pdev == NULL) {
+ dev_err(dev, "failed to allocate memory for module data\n");
+ ret = -ENOMEM;
+ goto err_get_pdev;
+ } else {
+ info->pdev->dev = *dev;
+ }
+ if (sec_nfc_clk_parse_dt(info) < 0) {
+ dev_err(dev, "failed to get clock information\n");
+ ret = -ENOMEM;
+ goto err_gpio_clk_parse;
+ }
+#endif
+#endif
+
+ ret = gpio_request(pdata->ven, "nfc_ven");
+ if (ret) {
+ dev_err(dev, "failed to get gpio ven\n");
+ goto err_gpio_ven;
+ }
+ gpio_direction_output(pdata->ven, SEC_NFC_PW_OFF);
+
+ if (pdata->firm)
+ {
+ ret = gpio_request(pdata->firm, "nfc_firm");
+ if (ret) {
+ dev_err(dev, "failed to get gpio firm\n");
+ goto err_gpio_firm;
+ }
+ gpio_direction_output(pdata->firm, SEC_NFC_FW_OFF);
+ }
+
+ dev_dbg(dev, "%s: success info: %p, pdata %p\n", __func__, info, pdata);
+
+ return 0;
+
+err_gpio_firm:
+ gpio_free(pdata->ven);
+err_gpio_ven:
+#ifdef CONFIG_SEC_NFC_CLK_REQ
+#if defined(CONFIG_SOC_EXYNOS5430) || defined(CONFIG_SOC_EXYNOS5433) || \
+ defined(CONFIG_SOC_EXYNOS7420)
+err_gpio_clk_parse:
+ kfree(info->pdev);
+err_get_pdev:
+#endif
+#endif
+err_dev_reg:
+ kfree(info);
+err_info_alloc:
+err_pdata:
+ return ret;
+}
+
+static int __sec_nfc_remove(struct device *dev)
+{
+ struct sec_nfc_info *info = dev_get_drvdata(dev);
+ struct sec_nfc_platform_data *pdata = info->pdata;
+
+ dev_dbg(info->dev, "%s\n", __func__);
+
+ misc_deregister(&info->miscdev);
+ sec_nfc_set_mode(info, SEC_NFC_MODE_OFF);
+ gpio_set_value(pdata->firm, 0);
+ gpio_free(pdata->ven);
+ if (pdata->firm) gpio_free(pdata->firm);
+
+#ifdef CONFIG_SEC_NFC_CLK_REQ
+#if defined(CONFIG_SOC_EXYNOS5430) || defined(CONFIG_SOC_EXYNOS5433) || \
+ defined(CONFIG_SOC_EXYNOS7420)
+ kfree(info->pdev);
+#endif
+#endif
+ kfree(info);
+
+ return 0;
+}
+
+#ifdef CONFIG_SEC_NFC_IF_I2C
+MODULE_DEVICE_TABLE(i2c, sec_nfc_id_table);
+typedef struct i2c_driver sec_nfc_driver_type;
+#define SEC_NFC_INIT(driver) i2c_add_driver(driver);
+#define SEC_NFC_EXIT(driver) i2c_del_driver(driver);
+
+static int sec_nfc_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret = 0;
+
+ ret = __sec_nfc_probe(&client->dev);
+ if (ret)
+ return ret;
+
+ if (sec_nfc_i2c_probe(client))
+ __sec_nfc_remove(&client->dev);
+
+ return ret;
+}
+
+static int sec_nfc_remove(struct i2c_client *client)
+{
+ sec_nfc_i2c_remove(&client->dev);
+ return __sec_nfc_remove(&client->dev);
+}
+
+static struct i2c_device_id sec_nfc_id_table[] = {
+ { SEC_NFC_DRIVER_NAME, 0 },
+ { }
+};
+
+#else /* CONFIG_SEC_NFC_IF_I2C */
+MODULE_DEVICE_TABLE(platform, sec_nfc_id_table);
+typedef struct platform_driver sec_nfc_driver_type;
+#define SEC_NFC_INIT(driver) platform_driver_register(driver);
+#define SEC_NFC_EXIT(driver) platform_driver_unregister(driver);
+
+static int sec_nfc_probe(struct platform_device *pdev)
+{
+ return __sec_nfc_probe(&pdev->dev);
+}
+
+static int sec_nfc_remove(struct platform_device *pdev)
+{
+ return __sec_nfc_remove(&pdev->dev);
+}
+
+static struct platform_device_id sec_nfc_id_table[] = {
+ { SEC_NFC_DRIVER_NAME, 0 },
+ { }
+};
+
+#endif /* CONFIG_SEC_NFC_IF_I2C */
+
+#ifdef CONFIG_OF
+static struct of_device_id nfc_match_table[] = {
+ { .compatible = SEC_NFC_DRIVER_NAME,},
+ {},
+};
+#else
+#define nfc_match_table NULL
+#endif
+
+static sec_nfc_driver_type sec_nfc_driver = {
+ .probe = sec_nfc_probe,
+ .id_table = sec_nfc_id_table,
+ .remove = sec_nfc_remove,
+ .driver = {
+ .name = SEC_NFC_DRIVER_NAME,
+#ifdef CONFIG_PM
+ .pm = &sec_nfc_pm_ops,
+#endif
+ .of_match_table = nfc_match_table,
+ },
+};
+
+static int __init sec_nfc_init(void)
+{
+ return SEC_NFC_INIT(&sec_nfc_driver);
+}
+
+static void __exit sec_nfc_exit(void)
+{
+ SEC_NFC_EXIT(&sec_nfc_driver);
+}
+
+module_init(sec_nfc_init);
+module_exit(sec_nfc_exit);
+
+MODULE_DESCRIPTION("Samsung sec_nfc driver");
+MODULE_LICENSE("GPL");