diff options
author | Gaurav Singhal <gsinghal@codeaurora.org> | 2016-05-05 13:08:19 +0530 |
---|---|---|
committer | Jeevan Shriram <jshriram@codeaurora.org> | 2016-05-18 13:37:18 -0700 |
commit | c9f8e5943e6b50384d9bc8e4fc1b19c98e0c7a91 (patch) | |
tree | c439496adc52e871af94d989291132eb9d464a84 /drivers | |
parent | ef13a52535f7d768ace70d3d725ee19addf59931 (diff) |
NFC: Add snapshot of NQxxx NFC driver
Add the latest version of NQxxx NFC driver
from msm-3.18.
This change is a combination of following changes:
1) NFC: add NQxxxx driver
commit <356203701b7fd61b2d9776fac4fac6427735248b>
2) NFC: change reset and read flow
commit <7620346454865b81d7086167d531aea7bb716926>
3) NFC: Enable DMA and CLK_REQ gpio config issue fix
commit <150dbf117709b5677f86e5ced86b468731019b8b>
4) NFC: Fix function descriptions
commit <c0248d70200c8e09a983758750632b7a75e422d3>
5) nq-nci: enable NFCC hardware check and clock to NQxx
commit <2a92c1d6135f2d1e8fe3f2afcd290a2b1311a5a2>
6) nq-nci: XO shut down issue fix
commit <8938151d4650fca6d42efdbce138aea9bad7eca0>
7) NFC: Remove sleep from irq handler
commit <8ea2c805108cbf59b8e2abf87ee207fbf08fad97>
8) NFC: Remove DMA allocation and stack use in write
commit <c1552090e4c46e1eeca756d0a7b4427f94eab0c3>
CRs-Fixed: 890678, 892310, 955860, 968399, 993292
Change-Id: Ibb861ebdc63d45699369e23c077589d37e024b5e
Signed-off-by: Gaurav Singhal <gsinghal@codeaurora.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/nfc/Kconfig | 8 | ||||
-rw-r--r-- | drivers/nfc/Makefile | 1 | ||||
-rw-r--r-- | drivers/nfc/nq-nci.c | 861 | ||||
-rw-r--r-- | drivers/nfc/nq-nci.h | 44 |
4 files changed, 914 insertions, 0 deletions
diff --git a/drivers/nfc/Kconfig b/drivers/nfc/Kconfig index 0d6003dee3af..f629eef26183 100644 --- a/drivers/nfc/Kconfig +++ b/drivers/nfc/Kconfig @@ -77,3 +77,11 @@ source "drivers/nfc/st-nci/Kconfig" source "drivers/nfc/nxp-nci/Kconfig" source "drivers/nfc/s3fwrn5/Kconfig" endmenu + +config NFC_NQ + tristate "QTI NCI based NFC Controller Driver for NQx" + depends on I2C + help + This enables the NFC driver for NQx based devices. + This is for i2c connected version. NCI protocol logic + resides in the usermode and it has no other NFC dependencies. diff --git a/drivers/nfc/Makefile b/drivers/nfc/Makefile index e3621416a48e..86a8fbaf0104 100644 --- a/drivers/nfc/Makefile +++ b/drivers/nfc/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_NFC_ST21NFCA) += st21nfca/ obj-$(CONFIG_NFC_ST_NCI) += st-nci/ obj-$(CONFIG_NFC_NXP_NCI) += nxp-nci/ obj-$(CONFIG_NFC_S3FWRN5) += s3fwrn5/ +obj-$(CONFIG_NFC_NQ) += nq-nci.o diff --git a/drivers/nfc/nq-nci.c b/drivers/nfc/nq-nci.c new file mode 100644 index 000000000000..9fd1d3e4d42a --- /dev/null +++ b/drivers/nfc/nq-nci.c @@ -0,0 +1,861 @@ +/* Copyright (c) 2016, The Linux Foundation. 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 version 2 and + * only 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/reboot.h> +#include <linux/slab.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/spinlock.h> +#include <linux/of_gpio.h> +#include <linux/of_device.h> +#include <linux/uaccess.h> +#include "nq-nci.h" +#include <linux/clk.h> +#ifdef CONFIG_COMPAT +#include <linux/compat.h> +#endif + +struct nqx_platform_data { + unsigned int irq_gpio; + unsigned int en_gpio; + unsigned int clkreq_gpio; + unsigned int firm_gpio; + const char *clk_src_name; +}; + +static const struct of_device_id msm_match_table[] = { + {.compatible = "qcom,nq-nci"}, + {} +}; + +MODULE_DEVICE_TABLE(of, msm_match_table); + +#define MAX_BUFFER_SIZE (320) +#define WAKEUP_SRC_TIMEOUT (2000) + +struct nqx_dev { + wait_queue_head_t read_wq; + struct mutex read_mutex; + struct i2c_client *client; + struct miscdevice nqx_device; + /* NFC GPIO variables */ + unsigned int irq_gpio; + unsigned int en_gpio; + unsigned int firm_gpio; + unsigned int clkreq_gpio; + /* NFC_IRQ state */ + bool irq_enabled; + spinlock_t irq_enabled_lock; + unsigned int count_irq; + /* Initial CORE RESET notification */ + unsigned int core_reset_ntf; + /* CLK control */ + bool clk_run; + struct clk *s_clk; + /* read buffer*/ + size_t kbuflen; + u8 *kbuf; + +}; + +static int nfcc_reboot(struct notifier_block *notifier, unsigned long val, + void *v); +/*clock enable function*/ +static int nqx_clock_select(struct nqx_dev *nqx_dev); +/*clock disable function*/ +static int nqx_clock_deselect(struct nqx_dev *nqx_dev); +static struct notifier_block nfcc_notifier = { + .notifier_call = nfcc_reboot, + .next = NULL, + .priority = 0 +}; + +unsigned int disable_ctrl; + +static void nqx_init_stat(struct nqx_dev *nqx_dev) +{ + nqx_dev->count_irq = 0; +} + +static void nqx_disable_irq(struct nqx_dev *nqx_dev) +{ + unsigned long flags; + + spin_lock_irqsave(&nqx_dev->irq_enabled_lock, flags); + if (nqx_dev->irq_enabled) { + disable_irq_nosync(nqx_dev->client->irq); + nqx_dev->irq_enabled = false; + } + spin_unlock_irqrestore(&nqx_dev->irq_enabled_lock, flags); +} + +static void nqx_enable_irq(struct nqx_dev *nqx_dev) +{ + unsigned long flags; + + spin_lock_irqsave(&nqx_dev->irq_enabled_lock, flags); + if (!nqx_dev->irq_enabled) { + nqx_dev->irq_enabled = true; + enable_irq(nqx_dev->client->irq); + } + spin_unlock_irqrestore(&nqx_dev->irq_enabled_lock, flags); +} + +static irqreturn_t nqx_dev_irq_handler(int irq, void *dev_id) +{ + struct nqx_dev *nqx_dev = dev_id; + unsigned long flags; + int ret; + + if (device_may_wakeup(&nqx_dev->client->dev) && + (nqx_dev->client->dev.power.is_suspended == true)) { + pm_wakeup_event(&nqx_dev->client->dev, WAKEUP_SRC_TIMEOUT); + } + ret = gpio_get_value(nqx_dev->irq_gpio); + if (!ret) { +#ifdef NFC_KERNEL_BU + dev_info(&nqx_dev->client->dev, + "nqx nfc : nqx_dev_irq_handler error = %d\n", ret); +#endif + return IRQ_HANDLED; + } + + spin_lock_irqsave(&nqx_dev->irq_enabled_lock, flags); + nqx_dev->count_irq++; + spin_unlock_irqrestore(&nqx_dev->irq_enabled_lock, flags); + wake_up(&nqx_dev->read_wq); + + return IRQ_HANDLED; +} + +static ssize_t nfc_read(struct file *filp, char __user *buf, + size_t count, loff_t *offset) +{ + struct nqx_dev *nqx_dev = filp->private_data; + unsigned char *tmp = NULL; + int ret; + int irq_gpio_val = 0; + + if (count > nqx_dev->kbuflen) + count = nqx_dev->kbuflen; + + dev_dbg(&nqx_dev->client->dev, "%s : reading %zu bytes.\n", + __func__, count); + + mutex_lock(&nqx_dev->read_mutex); + + irq_gpio_val = gpio_get_value_cansleep(nqx_dev->irq_gpio); + if (irq_gpio_val == 0) { + if (filp->f_flags & O_NONBLOCK) { + dev_err(&nqx_dev->client->dev, + ":f_falg has O_NONBLOCK. EAGAIN\n"); + ret = -EAGAIN; + goto err; + } + nqx_dev->irq_enabled = true; + enable_irq(nqx_dev->client->irq); + if (gpio_get_value_cansleep(nqx_dev->irq_gpio)) { + nqx_disable_irq(nqx_dev); + } else { + ret = wait_event_interruptible(nqx_dev->read_wq, + gpio_get_value(nqx_dev->irq_gpio)); + nqx_disable_irq(nqx_dev); + if (ret) + goto err; + } + } + + tmp = nqx_dev->kbuf; + + memset(tmp, 0x00, count); + /* Read data */ + ret = i2c_master_recv(nqx_dev->client, tmp, count); + + mutex_unlock(&nqx_dev->read_mutex); + + if (ret < 0) { + dev_err(&nqx_dev->client->dev, + "%s: i2c_master_recv returned %d\n", __func__, ret); + return ret; + } + if (ret > count) { + dev_err(&nqx_dev->client->dev, + "%s: received too many bytes from i2c (%d)\n", + __func__, ret); + return -EIO; + } +#ifdef NFC_KERNEL_BU + dev_dbg(&nqx_dev->client->dev, "%s : NfcNciRx %x %x %x\n", + __func__, tmp[0], tmp[1], tmp[2]); +#endif + if (copy_to_user(buf, tmp, ret)) { + dev_warn(&nqx_dev->client->dev, + "%s : failed to copy to user space\n", __func__); + return -EFAULT; + } + return ret; + +err: + mutex_unlock(&nqx_dev->read_mutex); + return ret; +} + +static ssize_t nfc_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offset) +{ + struct nqx_dev *nqx_dev = filp->private_data; + char *tmp; + int ret = 0; + + if (count > nqx_dev->kbuflen) { + dev_err(&nqx_dev->client->dev, "%s: out of memory\n", + __func__); + return -ENOMEM; + } + + tmp = memdup_user(buf, count); + + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + + ret = i2c_master_send(nqx_dev->client, tmp, count); + if (ret != count) { + dev_err(&nqx_dev->client->dev, + "%s: failed to write %d\n", __func__, ret); + ret = -EIO; + } +#ifdef NFC_KERNEL_BU + dev_dbg(&nqx_dev->client->dev, + "%s : i2c-%d: NfcNciTx %x %x %x\n", + __func__, iminor(file_inode(filp)), + tmp[0], tmp[1], tmp[2]); +#endif + usleep_range(1000, 1100); + kfree(tmp); + return ret; +} + +static int nfc_open(struct inode *inode, struct file *filp) +{ + int ret = 0; + struct nqx_dev *nqx_dev = container_of(filp->private_data, + struct nqx_dev, nqx_device); + + filp->private_data = nqx_dev; + nqx_init_stat(nqx_dev); + /* Enable interrupts from NFCC NFC_INT new NCI data available */ + nqx_enable_irq(nqx_dev); + + dev_dbg(&nqx_dev->client->dev, + "%s: %d,%d\n", __func__, imajor(inode), iminor(inode)); + return ret; +} + +/** + * nfc_ioctl_power_states() - power control + * @filp: pointer to the file descriptor + * @arg: mode that we want to move to + * + * Device power control. Depending on the arg value, device moves to + * different states + * (arg = 0): NFC_ENABLE GPIO = 0, FW_DL GPIO = 0 + * (arg = 1): NFC_ENABLE GPIO = 1, FW_DL GPIO = 0 + * (arg = 2): FW_DL GPIO = 1 + * + * Return: -ENOIOCTLCMD if arg is not supported, 0 in any other case + */ +int nfc_ioctl_power_states(struct file *filp, unsigned long arg) +{ + int r = 0; + struct nqx_dev *nqx_dev = filp->private_data; + + if (arg == 0) { + /* We are attempting a hardware reset so let us disable + * interrupts to avoid spurious notifications to upper + * layers. + */ + nqx_disable_irq(nqx_dev); + dev_dbg(&nqx_dev->client->dev, + "gpio_set_value disable: %s: info: %p\n", + __func__, nqx_dev); + if (gpio_is_valid(nqx_dev->firm_gpio)) + gpio_set_value(nqx_dev->firm_gpio, 0); + gpio_set_value(nqx_dev->en_gpio, 0); + r = nqx_clock_deselect(nqx_dev); + if (r < 0) + dev_err(&nqx_dev->client->dev, "unable to disable clock\n"); + /* hardware dependent delay */ + msleep(100); + } else if (arg == 1) { + dev_dbg(&nqx_dev->client->dev, + "gpio_set_value enable: %s: info: %p\n", + __func__, nqx_dev); + if (gpio_is_valid(nqx_dev->firm_gpio)) + gpio_set_value(nqx_dev->firm_gpio, 0); + gpio_set_value(nqx_dev->en_gpio, 1); + r = nqx_clock_select(nqx_dev); + if (r < 0) + dev_err(&nqx_dev->client->dev, "unable to enable clock\n"); + + msleep(20); + } else if (arg == 2) { + /* We are switching to Dowload Mode, toggle the enable pin + * in order to set the NFCC in the new mode + */ + gpio_set_value(nqx_dev->en_gpio, 1); + msleep(20); + if (gpio_is_valid(nqx_dev->firm_gpio)) + gpio_set_value(nqx_dev->firm_gpio, 1); + msleep(20); + gpio_set_value(nqx_dev->en_gpio, 0); + msleep(100); + gpio_set_value(nqx_dev->en_gpio, 1); + msleep(20); + } else { + r = -ENOIOCTLCMD; + } + return r; +} + +#ifdef CONFIG_COMPAT +static long nfc_compat_ioctl(struct file *pfile, unsigned int cmd, + unsigned long arg) +{ + long r = 0; + + arg = (compat_u64)arg; + switch (cmd) { + case NFC_SET_PWR: + nfc_ioctl_power_states(pfile, arg); + break; + case SET_RX_BLOCK: + break; + case SET_EMULATOR_TEST_POINT: + break; + default: + r = -ENOTTY; + } + return r; +} +#endif + +/** + * nfc_ioctl_core_reset_ntf() + * @filp: pointer to the file descriptor + * + * Allows callers to determine if a CORE_RESET_NTF has arrived + * + * Return: the value of variable core_reset_ntf + */ +int nfc_ioctl_core_reset_ntf(struct file *filp) +{ + struct nqx_dev *nqx_dev = filp->private_data; + + dev_dbg(&nqx_dev->client->dev, "%s: returning = %d\n", __func__, + nqx_dev->core_reset_ntf); + return nqx_dev->core_reset_ntf; +} + +static long nfc_ioctl(struct file *pfile, unsigned int cmd, + unsigned long arg) +{ + int r = 0; + + switch (cmd) { + case NFC_SET_PWR: + r = nfc_ioctl_power_states(pfile, arg); + break; + case NFC_CLK_REQ: + break; + case SET_RX_BLOCK: + break; + case SET_EMULATOR_TEST_POINT: + break; + case NFCC_INITIAL_CORE_RESET_NTF: + r = nfc_ioctl_core_reset_ntf(pfile); + break; + default: + r = -ENOIOCTLCMD; + } + return r; +} + +static const struct file_operations nfc_dev_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = nfc_read, + .write = nfc_write, + .open = nfc_open, + .unlocked_ioctl = nfc_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = nfc_compat_ioctl +#endif +}; + +/* Check for availability of NQ_ NFC controller hardware */ +static int nfcc_hw_check(struct i2c_client *client, unsigned int enable_gpio) +{ + int ret = 0; + + unsigned char raw_nci_reset_cmd[] = {0x20, 0x00, 0x01, 0x00}; + unsigned char nci_reset_rsp[6]; + + /* making sure that the NFCC starts in a clean state. */ + gpio_set_value(enable_gpio, 0);/* ULPM: Disable */ + /* hardware dependent delay */ + msleep(20); + gpio_set_value(enable_gpio, 1);/* HPD : Enable*/ + /* hardware dependent delay */ + msleep(20); + + /* send NCI CORE RESET CMD with Keep Config parameters */ + ret = i2c_master_send(client, raw_nci_reset_cmd, + sizeof(raw_nci_reset_cmd)); + if (ret < 0) { + dev_err(&client->dev, + "%s: - i2c_master_send Error\n", __func__); + goto err_nfcc_hw_check; + } + /* hardware dependent delay */ + msleep(30); + + /* Read Response of RESET command */ + ret = i2c_master_recv(client, nci_reset_rsp, + sizeof(nci_reset_rsp)); + dev_err(&client->dev, + "%s: - nq - reset cmd answer : NfcNciRx %x %x %x\n", + __func__, nci_reset_rsp[0], + nci_reset_rsp[1], nci_reset_rsp[2]); + if (ret < 0) { + dev_err(&client->dev, + "%s: - i2c_master_recv Error\n", __func__); + goto err_nfcc_hw_check; + } + gpio_set_value(enable_gpio, 0);/* ULPM: Disable */ + ret = 0; + goto done; + +err_nfcc_hw_check: + ret = -ENXIO; + dev_err(&client->dev, + "%s: - NFCC HW not available\n", __func__); +done: + return ret; +} + +/* + * Routine to enable clock. + * this routine can be extended to select from multiple + * sources based on clk_src_name. +*/ +static int nqx_clock_select(struct nqx_dev *nqx_dev) +{ + int r = 0; + + nqx_dev->s_clk = + clk_get(&nqx_dev->client->dev, "ref_clk"); + + if (nqx_dev->s_clk == NULL) + goto err_clk; + + if (nqx_dev->clk_run == false) + r = clk_prepare_enable(nqx_dev->s_clk); + + if (r) + goto err_clk; + + nqx_dev->clk_run = true; + + return r; + +err_clk: + r = -1; + return r; +} +/* + * Routine to disable clocks +*/ +static int nqx_clock_deselect(struct nqx_dev *nqx_dev) +{ + int r = -1; + + if (nqx_dev->s_clk != NULL) { + if (nqx_dev->clk_run == true) { + clk_disable_unprepare(nqx_dev->s_clk); + nqx_dev->clk_run = false; + } + return 0; + } + return r; +} + +static int nfc_parse_dt(struct device *dev, struct nqx_platform_data *pdata) +{ + int r = 0; + struct device_node *np = dev->of_node; + + pdata->en_gpio = of_get_named_gpio(np, "qcom,nq-ven", 0); + if ((!gpio_is_valid(pdata->en_gpio))) + return -EINVAL; + disable_ctrl = pdata->en_gpio; + + pdata->irq_gpio = of_get_named_gpio(np, "qcom,nq-irq", 0); + if ((!gpio_is_valid(pdata->irq_gpio))) + return -EINVAL; + + pdata->firm_gpio = of_get_named_gpio(np, "qcom,nq-firm", 0); + if (!gpio_is_valid(pdata->firm_gpio)) { + dev_warn(dev, + "FIRM GPIO <OPTIONAL> error getting from OF node\n"); + pdata->firm_gpio = -EINVAL; + } + + r = of_property_read_string(np, "qcom,clk-src", &pdata->clk_src_name); + + pdata->clkreq_gpio = of_get_named_gpio(np, "qcom,nq-clkreq", 0); + + if (r) + return -EINVAL; + return r; +} + +static inline int gpio_input_init(const struct device * const dev, + const int gpio, const char * const gpio_name) +{ + int r = gpio_request(gpio, gpio_name); + + if (r) { + dev_err(dev, "unable to request gpio [%d]\n", gpio); + return r; + } + + r = gpio_direction_input(gpio); + if (r) + dev_err(dev, "unable to set direction for gpio [%d]\n", gpio); + + return r; +} + +static int nqx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int r = 0; + int irqn = 0; + struct nqx_platform_data *platform_data; + struct nqx_dev *nqx_dev; + + dev_dbg(&client->dev, "%s: enter\n", __func__); + if (client->dev.of_node) { + platform_data = devm_kzalloc(&client->dev, + sizeof(struct nqx_platform_data), GFP_KERNEL); + if (!platform_data) + return -ENOMEM; + r = nfc_parse_dt(&client->dev, platform_data); + if (r) + return r; + } else { + platform_data = client->dev.platform_data; + } + dev_dbg(&client->dev, + "%s, inside nfc-nci flags = %x\n", + __func__, client->flags); + if (platform_data == NULL) { + dev_err(&client->dev, "%s: failed\n", __func__); + return -ENODEV; + } + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "%s: need I2C_FUNC_I2C\n", __func__); + return -ENODEV; + } + nqx_dev = kzalloc(sizeof(*nqx_dev), GFP_KERNEL); + if (nqx_dev == NULL) + return -ENOMEM; + nqx_dev->client = client; + nqx_dev->kbuflen = MAX_BUFFER_SIZE; + nqx_dev->kbuf = kzalloc(MAX_BUFFER_SIZE, GFP_KERNEL); + if (!nqx_dev->kbuf) { + dev_err(&client->dev, + "failed to allocate memory for nqx_dev->kbuf\n"); + r = -ENOMEM; + goto err_free_dev; + } + + if (gpio_is_valid(platform_data->en_gpio)) { + r = gpio_request(platform_data->en_gpio, "nfc_reset_gpio"); + if (r) { + dev_err(&client->dev, + "%s: unable to request gpio [%d]\n", + __func__, + platform_data->en_gpio); + goto err_mem; + } + r = gpio_direction_output(platform_data->en_gpio, 0); + if (r) { + dev_err(&client->dev, + "%s: unable to set direction for gpio [%d]\n", + __func__, + platform_data->en_gpio); + goto err_en_gpio; + } + } else { + dev_err(&client->dev, "%s: dis gpio not provided\n", __func__); + goto err_mem; + } + + if (gpio_is_valid(platform_data->irq_gpio)) { + r = gpio_request(platform_data->irq_gpio, "nfc_irq_gpio"); + if (r) { + dev_err(&client->dev, "%s: unable to req irq gpio [%d]\n", + __func__, platform_data->irq_gpio); + goto err_en_gpio; + } + r = gpio_direction_input(platform_data->irq_gpio); + if (r) { + + dev_err(&client->dev, + "%s: unable to set direction for irq gpio [%d]\n", + __func__, + platform_data->irq_gpio); + goto err_irq; + } + irqn = gpio_to_irq(platform_data->irq_gpio); + if (irqn < 0) { + r = irqn; + goto err_irq; + } + client->irq = irqn; + } else { + dev_err(&client->dev, "%s: irq gpio not provided\n", __func__); + goto err_en_gpio; + } + if (gpio_is_valid(platform_data->firm_gpio)) { + r = gpio_request(platform_data->firm_gpio, + "nfc_firm_gpio"); + if (r) { + dev_err(&client->dev, + "%s: unable to request firm gpio [%d]\n", + __func__, platform_data->firm_gpio); + goto err_irq; + } + r = gpio_direction_output(platform_data->firm_gpio, 0); + if (r) { + dev_err(&client->dev, + "%s: cannot set direction for firm gpio [%d]\n", + __func__, platform_data->firm_gpio); + goto err_irq; + } + nqx_dev->firm_gpio = platform_data->firm_gpio; + } else { + dev_err(&client->dev, + "%s: firm gpio not provided\n", __func__); + } + if (gpio_is_valid(platform_data->clkreq_gpio)) { + r = gpio_request(platform_data->clkreq_gpio, + "nfc_clkreq_gpio"); + if (r) { + dev_err(&client->dev, + "%s: unable to request clk gpio [%d]\n", + __func__, platform_data->clkreq_gpio); + goto err_clkreq_gpio; + } + r = gpio_direction_input(platform_data->clkreq_gpio); + if (r) { + dev_err(&client->dev, + "%s: cannot set direction for clk gpio [%d]\n", + __func__, platform_data->clkreq_gpio); + goto err_clkreq_gpio; + } + nqx_dev->clkreq_gpio = platform_data->clkreq_gpio; + } else { + dev_err(&client->dev, + "%s: clkreq gpio not provided\n", __func__); + } + + nqx_dev->en_gpio = platform_data->en_gpio; + nqx_dev->irq_gpio = platform_data->irq_gpio; + nqx_dev->firm_gpio = platform_data->firm_gpio; + + /* init mutex and queues */ + init_waitqueue_head(&nqx_dev->read_wq); + mutex_init(&nqx_dev->read_mutex); + spin_lock_init(&nqx_dev->irq_enabled_lock); + + nqx_dev->nqx_device.minor = MISC_DYNAMIC_MINOR; + nqx_dev->nqx_device.name = "nq-nci"; + nqx_dev->nqx_device.fops = &nfc_dev_fops; + + r = misc_register(&nqx_dev->nqx_device); + if (r) { + dev_err(&client->dev, "%s: misc_register failed\n", __func__); + goto err_misc_register; + } + + /* NFC_INT IRQ */ + nqx_dev->irq_enabled = true; + r = request_irq(client->irq, nqx_dev_irq_handler, + IRQF_TRIGGER_RISING, client->name, nqx_dev); + if (r) { + dev_err(&client->dev, "%s: request_irq failed\n", __func__); + goto err_request_irq_failed; + } + nqx_disable_irq(nqx_dev); + + /* + * To be efficient we need to test whether nfcc hardware is physically + * present before attempting further hardware initialisation. + * + */ + + r = nfcc_hw_check(client, platform_data->en_gpio); + if (r) { + /* We don't think there is hardware switch NFC OFF */ + goto err_request_hw_check_failed; + } + + /* Register reboot notifier here */ + r = register_reboot_notifier(&nfcc_notifier); + if (r) { + dev_err(&client->dev, + "%s: cannot register reboot notifier(err = %d)\n", + __func__, r); + goto err_request_notifier_failed; + } + + device_init_wakeup(&client->dev, true); + device_set_wakeup_capable(&client->dev, true); + i2c_set_clientdata(client, nqx_dev); +#ifdef NFC_KERNEL_BU + r = nqx_clock_select(nqx_dev); + if (r < 0) { + dev_err(&client->dev, + "%s: nqx_clock_select failed\n", __func__); + goto err_request_notifier_failed; + } + gpio_set_value(platform_data->en_gpio, 1); +#endif + dev_err(&client->dev, + "%s: probing NFCC NQxxx exited successfully\n", + __func__); + return 0; + +err_request_notifier_failed: + unregister_reboot_notifier(&nfcc_notifier); +err_request_hw_check_failed: + /* make sure NFCC is not enabled */ + gpio_set_value(platform_data->en_gpio, 0); +err_request_irq_failed: + misc_deregister(&nqx_dev->nqx_device); +err_misc_register: + mutex_destroy(&nqx_dev->read_mutex); +err_clkreq_gpio: + gpio_free(platform_data->clkreq_gpio); +err_irq: + free_irq(client->irq, nqx_dev); + gpio_free(platform_data->irq_gpio); +err_en_gpio: + gpio_free(platform_data->en_gpio); +err_mem: + kfree(nqx_dev->kbuf); +err_free_dev: + kfree(nqx_dev); + dev_err(&client->dev, + "%s: probing nqxx failed, check hardware\n", + __func__); + return r; +} + +static int nqx_remove(struct i2c_client *client) +{ + struct nqx_dev *nqx_dev; + + nqx_dev = i2c_get_clientdata(client); + free_irq(client->irq, nqx_dev); + misc_deregister(&nqx_dev->nqx_device); + mutex_destroy(&nqx_dev->read_mutex); + gpio_free(nqx_dev->irq_gpio); + gpio_free(nqx_dev->en_gpio); + kfree(nqx_dev->kbuf); + kfree(nqx_dev); + return 0; +} + +static int nqx_suspend(struct device *device) +{ + struct i2c_client *client = to_i2c_client(device); + struct nqx_dev *nqx_dev = i2c_get_clientdata(client); + + if (device_may_wakeup(&client->dev) && nqx_dev->irq_enabled) + enable_irq_wake(client->irq); + return 0; +} + +static int nqx_resume(struct device *device) +{ + struct i2c_client *client = to_i2c_client(device); + + if (device_may_wakeup(&client->dev)) + disable_irq_wake(client->irq); + return 0; +} + +static const struct i2c_device_id nqx_id[] = { + {"nqx-i2c", 0}, + {} +}; + +static const struct dev_pm_ops nfc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(nqx_suspend, nqx_resume) +}; + +static struct i2c_driver nqx = { + .id_table = nqx_id, + .probe = nqx_probe, + .remove = nqx_remove, + .driver = { + .owner = THIS_MODULE, + .name = "nq-nci", + .of_match_table = msm_match_table, + .pm = &nfc_pm_ops, + }, +}; + + +static int nfcc_reboot(struct notifier_block *notifier, unsigned long val, + void *v) +{ + gpio_set_value(disable_ctrl, 1); + return NOTIFY_OK; +} + +/* + * module load/unload record keeping + */ +static int __init nqx_dev_init(void) +{ + return i2c_add_driver(&nqx); +} +module_init(nqx_dev_init); + +static void __exit nqx_dev_exit(void) +{ + unregister_reboot_notifier(&nfcc_notifier); + i2c_del_driver(&nqx); +} +module_exit(nqx_dev_exit); + +MODULE_DESCRIPTION("NFC nqx"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nfc/nq-nci.h b/drivers/nfc/nq-nci.h new file mode 100644 index 000000000000..e9f4de0aee3a --- /dev/null +++ b/drivers/nfc/nq-nci.h @@ -0,0 +1,44 @@ +/* Copyright (c) 2016, The Linux Foundation. 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 version 2 and + * only 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. + */ + +#ifndef __NQ_NCI_H +#define __NQ_NCI_H + +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/version.h> + +#include <linux/semaphore.h> +#include <linux/completion.h> + +#include <linux/ioctl.h> +#include <linux/miscdevice.h> + +#define NFC_SET_PWR _IOW(0xE9, 0x01, unsigned int) +#define NFC_CLK_REQ _IOW(0xE9, 0x02, unsigned int) +#define SET_RX_BLOCK _IOW(0xE9, 0x04, unsigned int) +#define SET_EMULATOR_TEST_POINT _IOW(0xE9, 0x05, unsigned int) +#define NFCC_INITIAL_CORE_RESET_NTF _IOW(0xE9, 0x10, unsigned int) + +#define NFC_RX_BUFFER_CNT_START (0x0) +#define PAYLOAD_HEADER_LENGTH (0x3) +#define PAYLOAD_LENGTH_MAX (256) +#define BYTE (0x8) +#define NCI_IDENTIFIER (0x10) + +enum nfcc_initial_core_reset_ntf { + TIMEDOUT_INITIAL_CORE_RESET_NTF = 0, /* 0*/ + ARRIVED_INITIAL_CORE_RESET_NTF, /* 1 */ + DEFAULT_INITIAL_CORE_RESET_NTF, /*2*/ +}; + +#endif |