From 61d19800e8da9b8e39fb7e573f1f4238a2d8a3d3 Mon Sep 17 00:00:00 2001 From: ekkusa Date: Mon, 29 Aug 2022 15:39:26 +0000 Subject: [PATCH] misc: Import CXD224X NFC driver Signed-off-by: Sorayukii --- drivers/misc/Kconfig | 11 + drivers/misc/Makefile | 2 + drivers/misc/carillon/Kconfig | 5 + drivers/misc/carillon/Makefile | 2 + drivers/misc/carillon/bd7602/bd7602.c | 311 ++++++++ drivers/misc/carillon/bd7602/bd7602.h | 18 + drivers/misc/carillon/cxd224x/cxd224x-i2c.c | 843 ++++++++++++++++++++ drivers/misc/carillon/cxd224x/cxd224x.h | 47 ++ drivers/misc/carillon/main_module.c | 54 ++ drivers/misc/cxd224x-i2c.c | 739 +++++++++++++++++ include/linux/cxd224x.h | 29 + 11 files changed, 2061 insertions(+) create mode 100644 drivers/misc/carillon/Kconfig create mode 100644 drivers/misc/carillon/Makefile create mode 100644 drivers/misc/carillon/bd7602/bd7602.c create mode 100644 drivers/misc/carillon/bd7602/bd7602.h create mode 100644 drivers/misc/carillon/cxd224x/cxd224x-i2c.c create mode 100644 drivers/misc/carillon/cxd224x/cxd224x.h create mode 100644 drivers/misc/carillon/main_module.c create mode 100644 drivers/misc/cxd224x-i2c.c create mode 100644 include/linux/cxd224x.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index f3bf3ee62290..62e3df0df77b 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -869,6 +869,16 @@ config QPNP_MISC and the driver provides an API to check if this interrupt is available on the current PMIC chip. +config CXD224X_NFC + bool "CXD224X driver for FeliCa and NFC" + depends on I2C + default n + help + This option enables device driver support for Sony CXD224X + series. This driver enables the control of CXD224X series + device by using I2C. Furthermore, this driver enables the + control of PON using device file. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" @@ -882,6 +892,7 @@ source "drivers/misc/genwqe/Kconfig" source "drivers/misc/echo/Kconfig" source "drivers/misc/cxl/Kconfig" source "drivers/misc/fpr_FingerprintCard/Kconfig" +source "drivers/misc/carillon/Kconfig" endmenu config OKL4_LINK_SHBUF diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 4f16afe6b5f5..a96adeaa34b0 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_QSEECOM) += qseecom.o obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ +obj-$(CONFIG_CXD224X_NFC) += cxd224x-i2c.o obj-$(CONFIG_PANEL) += panel.o obj-$(CONFIG_QPNP_MISC) += qpnp-misc.o obj-$(CONFIG_FPR_FPC) += fpr_FingerprintCard/ @@ -91,3 +92,4 @@ $(obj)/lkdtm_rodata_objcopy.o: $(obj)/lkdtm_rodata.o FORCE $(call if_changed,objcopy) obj-$(CONFIG_OKL4_LINK_SHBUF) += okl4-link-shbuf.o +obj-$(CONFIG_NFC_CARILLON) += carillon/ diff --git a/drivers/misc/carillon/Kconfig b/drivers/misc/carillon/Kconfig new file mode 100644 index 000000000000..3f36035cae0d --- /dev/null +++ b/drivers/misc/carillon/Kconfig @@ -0,0 +1,5 @@ +comment "Sony Carillon NFC driver" + +config NFC_CARILLON + tristate "Sony Carillon protocol driver (I2C) devices" + default n diff --git a/drivers/misc/carillon/Makefile b/drivers/misc/carillon/Makefile new file mode 100644 index 000000000000..5e9857f500c8 --- /dev/null +++ b/drivers/misc/carillon/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_NFC_CARILLON) := sony_carillon_nfc.o +sony_carillon_nfc-objs := main_module.o cxd224x/cxd224x-i2c.o bd7602/bd7602.o \ No newline at end of file diff --git a/drivers/misc/carillon/bd7602/bd7602.c b/drivers/misc/carillon/bd7602/bd7602.c new file mode 100644 index 000000000000..06272b497df9 --- /dev/null +++ b/drivers/misc/carillon/bd7602/bd7602.c @@ -0,0 +1,311 @@ +/* drivers/misc/bd7602.c + * + * Author: Manabu Yoshida + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bd7602.h" + +#define BD7602_DEVICE_NAME "bd7602" +#define BD7602_MODE_MAX 0x04 + +struct bd7602_dev { + struct mutex mutex; + struct i2c_client *client; + struct miscdevice bd7602_device; + u8 mode; +}; + +static ssize_t bd7602_mode_store(struct bd7602_dev *bd7602_dev, void __user *buf) +{ + u8 mode; + int ret; + + ret = copy_from_user(&mode, buf, sizeof(mode)); + if (ret != 0) { + dev_err(&bd7602_dev->client->dev, "%s: Failed to copy data from user %d\n", + __func__, ret); + ret = -EIO; + goto out; + } + + if (BD7602_MODE_MAX < mode) { + dev_err(&bd7602_dev->client->dev, "%s: Invalid mode 0x%02x\n", __func__, mode); + ret = -EINVAL; + goto out; + } + + mutex_lock(&bd7602_dev->mutex); + bd7602_dev->mode = mode; + mutex_unlock(&bd7602_dev->mutex); + dev_info(&bd7602_dev->client->dev, "%s: mode: 0x%02x\n", __func__, bd7602_dev->mode); + +out: + return ret; +} + +static ssize_t bd7602_mode_show(struct bd7602_dev *bd7602_dev, void __user *buf) +{ + int ret; + + if (!buf) { + dev_err(&bd7602_dev->client->dev, "%s: invalid address\n", __func__); + return -EINVAL; + } + + mutex_lock(&bd7602_dev->mutex); + ret = copy_to_user(buf, &bd7602_dev->mode, sizeof(bd7602_dev->mode)); + if (ret != 0) { + dev_err(&bd7602_dev->client->dev, "%s: Failed to copy data to user %d\n", + __func__, ret); + ret = -EIO; + } + mutex_unlock(&bd7602_dev->mutex); + dev_info(&bd7602_dev->client->dev, "%s: mode: 0x%02x\n", __func__, bd7602_dev->mode); + + return ret; +} + +static ssize_t bd7602_value_store(struct bd7602_dev *bd7602_dev, void __user *buf) +{ + u8 data[2]; + int ret; + int len = 2; + + mutex_lock(&bd7602_dev->mutex); + data[0] = bd7602_dev->mode; + ret = copy_from_user(&data[1], buf, sizeof(u8)); + if (ret != 0) { + mutex_unlock(&bd7602_dev->mutex); + dev_err(&bd7602_dev->client->dev, "%s: Failed to copy data from user %d\n", + __func__, ret); + ret = -EIO; + goto out; + } + ret = i2c_master_send(bd7602_dev->client, data, len); + if (ret != len) { + mutex_unlock(&bd7602_dev->mutex); + dev_err(&bd7602_dev->client->dev, "%s: Failed to write %d\n", __func__, ret); + ret = -EIO; + goto out; + } + mutex_unlock(&bd7602_dev->mutex); + + dev_info(&bd7602_dev->client->dev, "%s: mode:0x%02x value:0x%02x\n", + __func__, data[0], data[1]); + +out: + return ret > 0 ? 0 : ret; +} + +static ssize_t bd7602_value_show(struct bd7602_dev *bd7602_dev, void __user *buf) +{ + u8 data; + int ret; + + if (!buf) { + dev_err(&bd7602_dev->client->dev, "%s: invalid address\n", __func__); + ret = -EINVAL; + goto out; + } + + mutex_lock(&bd7602_dev->mutex); + ret = i2c_master_send(bd7602_dev->client, &bd7602_dev->mode, + sizeof(bd7602_dev->mode)); + if (sizeof(bd7602_dev->mode) != ret) { + mutex_unlock(&bd7602_dev->mutex); + dev_err(&bd7602_dev->client->dev, "%s: Failed to write %d\n", __func__, ret); + ret = -EIO; + goto out; + } + + ret = i2c_master_recv(bd7602_dev->client, &data, sizeof(data)); + if (sizeof(data) != ret) { + mutex_unlock(&bd7602_dev->mutex); + dev_err(&bd7602_dev->client->dev, "%s: Failed to read %d\n", __func__, ret); + ret = -EIO; + goto out; + } + + ret = copy_to_user(buf, &data, sizeof(data)); + if (ret != 0) { + mutex_unlock(&bd7602_dev->mutex); + dev_err(&bd7602_dev->client->dev, "%s: Failed to copy data to user %d\n", + __func__, ret); + ret = -EIO; + goto out; + } + mutex_unlock(&bd7602_dev->mutex); + + dev_info(&bd7602_dev->client->dev, "%s: mode:0x%02x value:0x%02x\n", + __func__, bd7602_dev->mode, data); + +out: + return ret > 0 ? 0 : ret; +} + +static long bd7602_dev_unlocked_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct bd7602_dev *bd7602_dev = filp->private_data; + int ret = -EINVAL; + void __user *data = (void __user *)arg; + + if (!bd7602_dev) { + return -ENODEV; + } + + switch (cmd) { + case BD7602_MODE_STORE: + ret = bd7602_mode_store(bd7602_dev, data); + break; + + case BD7602_MODE_SHOW: + ret = bd7602_mode_show(bd7602_dev, data); + break; + + case BD7602_VALUE_STORE: + ret = bd7602_value_store(bd7602_dev, data); + break; + + case BD7602_VALUE_SHOW: + ret = bd7602_value_show(bd7602_dev, data); + break; + + default: + dev_err(&bd7602_dev->client->dev, + "%s, unknown cmd (%x, %lx)\n", __func__, cmd, arg); + } + + return ret; +} + +static int bd7602_dev_open(struct inode *inode, struct file *filp) +{ + struct bd7602_dev *bd7602_dev = container_of(filp->private_data, + struct bd7602_dev, + bd7602_device); + filp->private_data = bd7602_dev; + + dev_info(&bd7602_dev->client->dev, "%s, open\n", __func__); + + return 0; +} + +const struct file_operations bd7602_dev_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = bd7602_dev_open, + .unlocked_ioctl = bd7602_dev_unlocked_ioctl +}; + +static int bd7602_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct bd7602_dev *bd7602_dev; + + dev_info(&client->dev, "%s, probing bd7602 driver flags = %x\n", + __func__, client->flags); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "need I2C_FUNC_I2C\n"); + ret = -ENODEV; + goto err_exit; + } + + bd7602_dev = kzalloc(sizeof(*bd7602_dev), GFP_KERNEL); + if (bd7602_dev == NULL) { + dev_err(&client->dev, + "failed to allocate memory for module data\n"); + ret = -ENOMEM; + goto err_kzalloc; + } + + bd7602_dev->client = client; + i2c_set_clientdata(client, bd7602_dev); + + mutex_init(&bd7602_dev->mutex); + + bd7602_dev->bd7602_device.minor = MISC_DYNAMIC_MINOR; + bd7602_dev->bd7602_device.name = "bd7602-i2c"; + bd7602_dev->bd7602_device.fops = &bd7602_dev_fops; + + ret = misc_register(&bd7602_dev->bd7602_device); + if (ret) { + dev_err(&client->dev, "misc_register failed\n"); + goto err_misc_register; + } + + dev_info(&client->dev, + "%s, probing bd7602 driver exited successfully\n", + __func__); + return 0; + +err_misc_register: + mutex_destroy(&bd7602_dev->mutex); + i2c_set_clientdata(client, NULL); + kzfree(bd7602_dev); +err_kzalloc: +err_exit: + return ret; +} + +static int bd7602_remove(struct i2c_client *client) +{ + struct bd7602_dev *bd7602_dev = i2c_get_clientdata(client); + + mutex_destroy(&bd7602_dev->mutex); + i2c_set_clientdata(client, NULL); + kzfree(bd7602_dev); + + return 0; +} + +static const struct i2c_device_id bd7602_id[] = { + { BD7602_DEVICE_NAME, 0 }, + { } +}; + +static struct of_device_id bd7602_match_table[] = { + { .compatible = "rohm,bd7602", }, + { }, +}; + +static struct i2c_driver bd7602_driver = { + .id_table = bd7602_id, + .probe = bd7602_probe, + .remove = bd7602_remove, + .driver = { + .owner = THIS_MODULE, + .name = BD7602_DEVICE_NAME, + .of_match_table = bd7602_match_table, + }, +}; + +int bd7602_dev_init(void) +{ + return i2c_add_driver(&bd7602_driver); +} + +void bd7602_dev_exit(void) +{ + i2c_del_driver(&bd7602_driver); +} + +MODULE_AUTHOR("Manabu Yoshida "); +MODULE_DESCRIPTION("ROHM BD7602 Power IC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/carillon/bd7602/bd7602.h b/drivers/misc/carillon/bd7602/bd7602.h new file mode 100644 index 000000000000..ccef56d3f69d --- /dev/null +++ b/drivers/misc/carillon/bd7602/bd7602.h @@ -0,0 +1,18 @@ +#ifndef _BD7602_H +#define _BD7602_H + +#define BD7602_MAGIC 'J' + +/* + * BD7602 MODE and VALUE via ioctl + * BD7602_MODE_STORE: store target register address + * BD7602_MODE_SHOW: retrieve current register address + * BD7602_VALUE_STORE: store register value + * BD7602_VALUE_SHOW: retrieve register value + */ +#define BD7602_MODE_STORE _IOW(BD7602_MAGIC, 0x01, char *) +#define BD7602_MODE_SHOW _IOR(BD7602_MAGIC, 0x02, char *) +#define BD7602_VALUE_STORE _IOW(BD7602_MAGIC, 0x03, char *) +#define BD7602_VALUE_SHOW _IOR(BD7602_MAGIC, 0x04, char *) + +#endif diff --git a/drivers/misc/carillon/cxd224x/cxd224x-i2c.c b/drivers/misc/carillon/cxd224x/cxd224x-i2c.c new file mode 100644 index 000000000000..fd80e4a85b59 --- /dev/null +++ b/drivers/misc/carillon/cxd224x/cxd224x-i2c.c @@ -0,0 +1,843 @@ +/* + * cxd224x-i2c.c - cxd224x NFC i2c driver + * + * Copyright (C) 2013- Sony Corporation. + * Copyright (C) 2012 Broadcom Corporation. + * Copyright (C) 2017 Sony Mobile Communications Inc. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * NOTE: This file has been modified by Sony Mobile Communications Inc. + * Modifications are licensed under the License. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cxd224x.h" + +#define CXD224X_WAKE_LOCK_TIMEOUT 1 /* wake timeout for HOSTINT (sec) */ + +/* do not change below */ +#define MAX_BUFFER_SIZE 780 + +/* Read data */ +#define PACKET_HEADER_SIZE_NCI (3) +#define PACKET_HEADER_SIZE_HCI (3) +#define PACKET_TYPE_NCI (16) +#define PACKET_TYPE_HCIEV (4) +#define MAX_PACKET_SIZE (PACKET_HEADER_SIZE_NCI + 255) + +/* RESET */ +#define RESET_ASSERT_MS (1) + +#define CXD224X_PINCTRL_ACTIVE "felica_active" +#define CXD224X_PINCTRL_SUSPEND "felica_suspend" + +struct cxd224x_dev { + wait_queue_head_t read_wq; + struct mutex read_mutex; + struct i2c_client *client; + struct miscdevice cxd224x_device; + struct cxd224x_platform_data *gpio; + bool irq_enabled; + struct mutex lock; + spinlock_t irq_enabled_lock; + unsigned int users; + unsigned int count_irq; + /* Driver message queue */ + struct workqueue_struct *wqueue; + struct work_struct qmsg; + struct pinctrl *pinctrl; + struct pinctrl_state *gpio_state_active; + struct pinctrl_state *gpio_state_suspend; +}; + +static struct cxd224x_dev *p_cxd224x_dev; +static void cxd224x_enable_pon(struct cxd224x_dev *cxd224x_dev); +static void cxd224x_conditional_disable_pon(struct cxd224x_dev *cxd224x_dev); + +#if defined(CONFIG_NFC_CXD224X_RST) || defined(CONFIG_NFC_CXD224X_RST_MODULE) +static void cxd224x_workqueue(struct work_struct *work) +{ + struct cxd224x_dev *cxd224x_dev = container_of(work, struct cxd224x_dev, qmsg); + unsigned long flags; + + dev_info(&cxd224x_dev->client->dev, "%s, xrst assert\n", __func__); + spin_lock_irqsave(&cxd224x_dev->irq_enabled_lock, flags); + gpio_set_value(cxd224x_dev->gpio->rst_gpio, CXDNFC_RST_ACTIVE); + cxd224x_dev->count_irq=0; /* clear irq */ + spin_unlock_irqrestore(&cxd224x_dev->irq_enabled_lock, flags); + + msleep(RESET_ASSERT_MS); + dev_info(&cxd224x_dev->client->dev, "%s, xrst deassert\n", __func__); + gpio_set_value(cxd224x_dev->gpio->rst_gpio, ~CXDNFC_RST_ACTIVE & 0x1); +} + +static int __init init_wqueue(struct cxd224x_dev *cxd224x_dev) +{ + INIT_WORK(&cxd224x_dev->qmsg, cxd224x_workqueue); + cxd224x_dev->wqueue = create_workqueue("cxd224x-i2c_wrokq"); + if (cxd224x_dev->wqueue == NULL) + return -EBUSY; + return 0; +} +#endif /* CONFIG_NFC_CXD224X_RST */ + +static void cxd224x_init_stat(struct cxd224x_dev *cxd224x_dev) +{ + cxd224x_dev->count_irq = 0; +} + +static void cxd224x_disable_irq(struct cxd224x_dev *cxd224x_dev) +{ + unsigned long flags; + spin_lock_irqsave(&cxd224x_dev->irq_enabled_lock, flags); + if (cxd224x_dev->irq_enabled) { + disable_irq_nosync(cxd224x_dev->client->irq); + cxd224x_dev->irq_enabled = false; + } + spin_unlock_irqrestore(&cxd224x_dev->irq_enabled_lock, flags); +} + +static void cxd224x_enable_irq(struct cxd224x_dev *cxd224x_dev) +{ + unsigned long flags; + spin_lock_irqsave(&cxd224x_dev->irq_enabled_lock, flags); + if (!cxd224x_dev->irq_enabled) { + cxd224x_dev->irq_enabled = true; + enable_irq(cxd224x_dev->client->irq); + } + spin_unlock_irqrestore(&cxd224x_dev->irq_enabled_lock, flags); +} + +static irqreturn_t cxd224x_dev_irq_handler(int irq, void *dev_id) +{ + struct cxd224x_dev *cxd224x_dev = dev_id; + unsigned long flags; + + dev_info(&cxd224x_dev->client->dev, "%s\n", __func__); + + spin_lock_irqsave(&cxd224x_dev->irq_enabled_lock, flags); + cxd224x_dev->count_irq++; + if (cxd224x_dev->irq_enabled) { + cxd224x_enable_pon(cxd224x_dev); + } + spin_unlock_irqrestore(&cxd224x_dev->irq_enabled_lock, flags); + + wake_up(&cxd224x_dev->read_wq); + + return IRQ_HANDLED; +} + +static unsigned int cxd224x_dev_poll(struct file *filp, poll_table *wait) +{ + struct cxd224x_dev *cxd224x_dev = filp->private_data; + unsigned int mask = 0; + unsigned long flags; + + poll_wait(filp, &cxd224x_dev->read_wq, wait); + + spin_lock_irqsave(&cxd224x_dev->irq_enabled_lock, flags); + if (cxd224x_dev->count_irq > 0) + { + mask |= POLLIN | POLLRDNORM; + } + spin_unlock_irqrestore(&cxd224x_dev->irq_enabled_lock, flags); + + if(mask) + pm_wakeup_event(&cxd224x_dev->client->dev, + jiffies_to_msecs(CXD224X_WAKE_LOCK_TIMEOUT*HZ)); + + return mask; +} + +static ssize_t cxd224x_dev_read(struct file *filp, char __user *buf, + size_t count, loff_t *offset) +{ + struct cxd224x_dev *cxd224x_dev = filp->private_data; + unsigned char tmp[MAX_BUFFER_SIZE]; + int total, len, ret; + unsigned long flags; + + total = 0; + len = 0; + + if (count > MAX_BUFFER_SIZE) + count = MAX_BUFFER_SIZE; + + spin_lock_irqsave(&cxd224x_dev->irq_enabled_lock, flags); + if (cxd224x_dev->count_irq > 0) + cxd224x_dev->count_irq--; + spin_unlock_irqrestore(&cxd224x_dev->irq_enabled_lock, flags); + + mutex_lock(&cxd224x_dev->read_mutex); + + ret = i2c_master_recv(cxd224x_dev->client, tmp, 3); + if (ret == 3 && (tmp[0] != 0xff)) { + total = ret; + + len = tmp[PACKET_HEADER_SIZE_NCI-1]; + + /** make sure full packet fits in the buffer + **/ + if (len > 0 && (len + total) <= count) { + /** read the remainder of the packet. + **/ + ret = i2c_master_recv(cxd224x_dev->client, tmp+total, len); + if (ret == len) + total += len; + } + } + + mutex_unlock(&cxd224x_dev->read_mutex); + + if (total > count || copy_to_user(buf, tmp, total)) { + dev_err(&cxd224x_dev->client->dev, + "failed to copy to user space, total = %d\n", total); + total = -EFAULT; + } + + return total; +} + +static ssize_t cxd224x_dev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offset) +{ + struct cxd224x_dev *cxd224x_dev = filp->private_data; + char tmp[MAX_BUFFER_SIZE]; + int ret; + + if (count > MAX_BUFFER_SIZE) { + dev_err(&cxd224x_dev->client->dev, "out of memory\n"); + return -ENOMEM; + } + + if (copy_from_user(tmp, buf, count)) { + dev_err(&cxd224x_dev->client->dev, + "failed to copy from user space\n"); + return -EFAULT; + } + + mutex_lock(&cxd224x_dev->read_mutex); + /* Write data */ + + ret = i2c_master_send(cxd224x_dev->client, tmp, count); + if (ret != count) { + dev_err(&cxd224x_dev->client->dev, + "failed to write %d\n", ret); + ret = -EIO; + } + mutex_unlock(&cxd224x_dev->read_mutex); + + return ret; +} + +static int cxd224x_dev_open(struct inode *inode, struct file *filp) +{ + int ret = 0; + int call_enable = 0; + struct cxd224x_dev *cxd224x_dev = container_of(filp->private_data, + struct cxd224x_dev, + cxd224x_device); + filp->private_data = cxd224x_dev; + mutex_lock(&cxd224x_dev->lock); + if (!cxd224x_dev->users) + { + cxd224x_init_stat(cxd224x_dev); + call_enable = 1; + } + cxd224x_dev->users++; + if (call_enable) + cxd224x_enable_irq(cxd224x_dev); + mutex_unlock(&cxd224x_dev->lock); + + dev_info(&cxd224x_dev->client->dev, + "open %d,%d users=%d\n", imajor(inode), iminor(inode), cxd224x_dev->users); + + return ret; +} + +static int cxd224x_dev_release(struct inode *inode, struct file *filp) +{ + int ret = 0; + int call_disable = 0; + struct cxd224x_dev *cxd224x_dev = filp->private_data; + + mutex_lock(&cxd224x_dev->lock); + cxd224x_dev->users--; + if (!cxd224x_dev->users) + { + call_disable = 1; + } + if (call_disable) + { + cxd224x_disable_irq(cxd224x_dev); + gpio_set_value(cxd224x_dev->gpio->wake_gpio, 0); + } + mutex_unlock(&cxd224x_dev->lock); + + dev_info(&cxd224x_dev->client->dev, + "release %d,%d users=%d\n", imajor(inode), iminor(inode), cxd224x_dev->users); + + return ret; +} + +static void cxd224x_enable_pon(struct cxd224x_dev *cxd224x_dev) +{ + gpio_set_value(cxd224x_dev->gpio->wake_gpio, 1); +} + +static void cxd224x_conditional_disable_pon(struct cxd224x_dev *cxd224x_dev) +{ + unsigned long flag; + + spin_lock_irqsave(&cxd224x_dev->irq_enabled_lock, flag); + /* Do not disable PON when data is available to be read */ + if (cxd224x_dev->count_irq == 0) + gpio_set_value(cxd224x_dev->gpio->wake_gpio, 0); + spin_unlock_irqrestore(&cxd224x_dev->irq_enabled_lock, flag); +} + +static long cxd224x_dev_unlocked_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct cxd224x_dev *cxd224x_dev = filp->private_data; + + switch (cmd) { + case CXDNFC_RST_CTL: +#if defined(CONFIG_NFC_CXD224X_RST) || defined(CONFIG_NFC_CXD224X_RST_MODULE) + dev_info(&cxd224x_dev->client->dev, "%s, rst arg=%d\n", __func__, (int)arg); + return (queue_work(cxd224x_dev->wqueue, &cxd224x_dev->qmsg) ? 0 : 1); +#endif + break; + case CXDNFC_POWER_CTL: +#if defined(CONFIG_NFC_CXD224X_VEN) || defined(CONFIG_NFC_CXD224X_VEN_MODULE) + if (arg == 0) { + gpio_set_value(cxd224x_dev->en_gpio, 1); + } else if (arg == 1) { + gpio_set_value(cxd224x_dev->en_gpio, 0); + } else { + /* do nothing */ + } +#else + return 1; /* not support */ +#endif + break; + case CXDNFC_WAKE_CTL: + if (arg == 0) { + /* PON HIGH (normal power mode)*/ + cxd224x_enable_pon(cxd224x_dev); + } else if (arg == 1) { + /* PON LOW (low power mode) */ + cxd224x_conditional_disable_pon(cxd224x_dev); + } else { + /* do nothing */ + } + break; + default: + dev_err(&cxd224x_dev->client->dev, + "%s, unknown cmd (%x, %lx)\n", __func__, cmd, arg); + return 0; + } + + return 0; +} + +static const struct file_operations cxd224x_dev_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .poll = cxd224x_dev_poll, + .read = cxd224x_dev_read, + .write = cxd224x_dev_write, + .open = cxd224x_dev_open, + .release = cxd224x_dev_release, + .unlocked_ioctl = cxd224x_dev_unlocked_ioctl +}; + +static int cxd224x_pinctrl_init(struct device *dev, struct cxd224x_dev *cxd224x_dev) +{ + int ret = 0; + + cxd224x_dev->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR_OR_NULL(cxd224x_dev->pinctrl)) { + dev_err(dev, "error devm_pinctrl_get() failed err:%ld\n", + PTR_ERR(cxd224x_dev->pinctrl)); + ret = PTR_ERR(cxd224x_dev->pinctrl); + goto out; + } + + cxd224x_dev->gpio_state_active = pinctrl_lookup_state( + cxd224x_dev->pinctrl, CXD224X_PINCTRL_ACTIVE); + + if (IS_ERR_OR_NULL(cxd224x_dev->gpio_state_active)) { + ret = PTR_ERR(cxd224x_dev->gpio_state_active); + dev_info(dev, "note pinctrl_lookup_state(%s) err:%d\n", + CXD224X_PINCTRL_ACTIVE, ret); + goto out; + } + + cxd224x_dev->gpio_state_suspend = pinctrl_lookup_state( + cxd224x_dev->pinctrl, CXD224X_PINCTRL_SUSPEND); + + if (IS_ERR_OR_NULL(cxd224x_dev->gpio_state_suspend)) { + ret = PTR_ERR(cxd224x_dev->gpio_state_suspend); + dev_info(dev, "note pinctrl_lookup_state(%s) err:%d\n", + CXD224X_PINCTRL_ACTIVE, ret); + goto out; + } + +out: + return ret; +} + +static void cxd224x_pinctrl_select_state(struct device *dev, bool active) +{ + struct cxd224x_dev *cxd224x_dev = dev_get_drvdata(dev); + struct pinctrl_state *pins_state; + const char *pins_state_name; + + if (active) { + pins_state = cxd224x_dev->gpio_state_active; + pins_state_name = CXD224X_PINCTRL_ACTIVE; + } else { + pins_state = cxd224x_dev->gpio_state_suspend; + pins_state_name = CXD224X_PINCTRL_SUSPEND; + } + + if (!IS_ERR_OR_NULL(pins_state)) { + int ret = pinctrl_select_state(cxd224x_dev->pinctrl, + pins_state); + if (ret) + dev_err(dev, "error pinctrl_select_state(%s) err:%d\n", + pins_state_name, ret); + } else { + dev_err(dev, + "error pinctrl state-name:'%s' is not configured\n", + pins_state_name); + } +} + +#if defined(CONFIG_OF) +static int cxd224x_parse_dt(struct device *dev, + struct cxd224x_platform_data *pdata) +{ + int ret=0; + + /*nfc_int*/ + pdata->irq_gpio = of_get_named_gpio_flags(dev->of_node, "sony,nfc_int", 0,NULL); + if (pdata->irq_gpio < 0) { + pr_err( "failed to get \"nfc_int\"\n"); + goto dt_err; + } + +#if defined(CONFIG_NFC_CXD224X_VEN) || defined(CONFIG_NFC_CXD224X_VEN_MODULE) + pdata->en_gpio = of_get_named_gpio_flags(dev->of_node, "sony,nfc_ven", 0,NULL); + if (pdata->en_gpio< 0) { + pr_err( "failed to get \"nfc_ven\"\n"); + goto dt_err; + } +#endif + +#if defined(CONFIG_NFC_CXD224X_RST) || defined(CONFIG_NFC_CXD224X_RST_MODULE) + pdata->rst_gpio = of_get_named_gpio_flags(dev->of_node, "sony,nfc_rst", 0,NULL); + if (pdata->rst_gpio< 0) { + pr_err( "failed to get \"nfc_rst\"\n"); + goto dt_err; + } +#endif + + pdata->wake_gpio = of_get_named_gpio_flags(dev->of_node, "sony,nfc_wake", 0,NULL); + if (pdata->wake_gpio< 0) { + pr_err( "failed to get \"nfc_wake\"\n"); + goto dt_err; + } + return 0; + +dt_err: + return ret; +} +#endif + +static int cxd224x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct cxd224x_platform_data *platform_data=NULL; + struct cxd224x_dev *cxd224x_dev; + struct clk *felica_clk = NULL; + int irq_gpio_ok = 0; +#if defined(CONFIG_NFC_CXD224X_VEN) || defined(CONFIG_NFC_CXD224X_VEN_MODULE) + int en_gpio_ok = 0; +#endif +#if defined(CONFIG_NFC_CXD224X_RST) || defined(CONFIG_NFC_CXD224X_RST_MODULE) + int rst_gpio_ok = 0; +#endif + int wake_gpio_ok = 0; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "need I2C_FUNC_I2C\n"); + return -ENODEV; + } + +#if defined(CONFIG_OF) + platform_data = kzalloc(sizeof(struct cxd224x_platform_data), + GFP_KERNEL); + if (platform_data == NULL) { + dev_err(&client->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + ret = cxd224x_parse_dt(&client->dev, platform_data); + if (ret) { + dev_err(&client->dev, "failed to parse device tree\n"); + kfree(platform_data); + return -ENODEV; + } +#else + platform_data = client->dev.platform_data; + + dev_info(&client->dev, "%s, probing cxd224x driver flags = %x\n", __func__, client->flags); + if (platform_data == NULL) { + dev_err(&client->dev, "nfc probe fail\n"); + return -ENODEV; + } +#endif + dev_info(&client->dev, "%s, rst_gpio(%d)\n", __func__, platform_data->rst_gpio); + dev_info(&client->dev, "%s, ven_gpio(%d)\n", __func__, platform_data->en_gpio); + dev_info(&client->dev, "%s, irq_gpio(%d)\n", __func__, platform_data->irq_gpio); + dev_info(&client->dev, "%s, wake_gpio(%d)\n", __func__, platform_data->wake_gpio); + + irq_gpio_ok=1; + client->irq = gpio_to_irq(platform_data->irq_gpio); + if (client->irq<0) + { + dev_err(&client->dev, "%s, failed to allocate irq=%d\n", __func__, client->irq); + return -ENODEV; + } + dev_info(&client->dev, "%s, irq(%d)\n", __func__, client->irq); + +#if defined(CONFIG_NFC_CXD224X_VEN) || defined(CONFIG_NFC_CXD224X_VEN_MODULE) + ret = gpio_request_one(platform_data->en_gpio, GPIOF_OUT_INIT_LOW, "nfc_cen"); + if (ret) + goto err_exit; + en_gpio_ok=1; + ret = gpio_direction_output(platform_data->en_gpio, 0); + if (ret) + return -ENODEV; +#endif + +#if defined(CONFIG_NFC_CXD224X_RST) || defined(CONFIG_NFC_CXD224X_RST_MODULE) + ret = gpio_request_one(platform_data->rst_gpio, GPIOF_OUT_INIT_HIGH, "nfc_rst"); + if (ret) + goto err_exit; + rst_gpio_ok=1; + ret = gpio_direction_output(platform_data->rst_gpio, ~CXDNFC_RST_ACTIVE & 0x1); + if (ret) + return -ENODEV; + dev_info(&client->dev, "%s, xrst deassert\n", __func__); +#endif + + ret = gpio_request_one(platform_data->wake_gpio, GPIOF_OUT_INIT_LOW, "nfc_wake"); + if (ret) + goto err_exit; + wake_gpio_ok=1; + ret = gpio_direction_output(platform_data->wake_gpio,0); + + cxd224x_dev = kzalloc(sizeof(*cxd224x_dev), GFP_KERNEL); + if (cxd224x_dev == NULL) { + dev_err(&client->dev, + "failed to allocate memory for module data\n"); + ret = -ENOMEM; + goto err_exit; + } + + cxd224x_dev->client = client; + cxd224x_dev->gpio = platform_data; + cxd224x_dev->users =0; + + /* init mutex and queues */ + init_waitqueue_head(&cxd224x_dev->read_wq); + mutex_init(&cxd224x_dev->read_mutex); + mutex_init(&cxd224x_dev->lock); + spin_lock_init(&cxd224x_dev->irq_enabled_lock); + +#if defined(CONFIG_NFC_CXD224X_RST) || defined(CONFIG_NFC_CXD224X_RST_MODULE) + if (init_wqueue(cxd224x_dev) != 0) { + dev_err(&client->dev, "init workqueue failed\n"); + goto err_exit; + } +#endif + + ret = cxd224x_pinctrl_init(&client->dev, cxd224x_dev); + if (ret) { + dev_err(&client->dev, "pinctrl_init failed\n"); + goto err_pinctrl_init; + } + + cxd224x_dev->cxd224x_device.minor = MISC_DYNAMIC_MINOR; + cxd224x_dev->cxd224x_device.name = "cxd224x-i2c"; + cxd224x_dev->cxd224x_device.fops = &cxd224x_dev_fops; + + ret = misc_register(&cxd224x_dev->cxd224x_device); + if (ret) { + dev_err(&client->dev, "misc_register failed\n"); + goto err_misc_register; + } + + felica_clk = clk_get(&client->dev, "felica_clk"); + if (IS_ERR(felica_clk)) { + dev_info(&client->dev, "Couldn't get felica_clk, XTAL should be used\n"); + } else { + ret = clk_prepare_enable(felica_clk); + if (ret) { + dev_err(&client->dev, "failed to enable felica_clk\n"); + goto err_clk_enable; + } + } + + /* request irq. the irq is set whenever the chip has data available + * for reading. it is cleared when all data has been read. + */ + dev_info(&client->dev, "requesting IRQ %d\n", client->irq); + cxd224x_dev->irq_enabled = true; + ret = request_irq(client->irq, cxd224x_dev_irq_handler, + IRQF_TRIGGER_FALLING, client->name, cxd224x_dev); + if (ret) { + dev_err(&client->dev, "request_irq failed\n"); + goto err_request_irq_failed; + } + cxd224x_disable_irq(cxd224x_dev); + i2c_set_clientdata(client, cxd224x_dev); + cxd224x_pinctrl_select_state(&client->dev, true); + device_init_wakeup(&client->dev, 1); + p_cxd224x_dev = cxd224x_dev; + + dev_info(&client->dev, + "%s, probing cxd224x driver exited successfully\n", + __func__); + return 0; + +err_request_irq_failed: + misc_deregister(&cxd224x_dev->cxd224x_device); +err_clk_enable: + clk_put(felica_clk); +err_pinctrl_init: +err_misc_register: + mutex_destroy(&cxd224x_dev->read_mutex); + kfree(cxd224x_dev); +err_exit: + if(irq_gpio_ok) + gpio_free(platform_data->irq_gpio); +#if defined(CONFIG_NFC_CXD224X_VEN) || defined(CONFIG_NFC_CXD224X_VEN_MODULE) + if(en_gpio_ok) + gpio_free(platform_data->en_gpio); +#endif +#if defined(CONFIG_NFC_CXD224X_RST) || defined(CONFIG_NFC_CXD224X_RST_MODULE) + if(rst_gpio_ok) + gpio_free(platform_data->rst_gpio); +#endif + if(wake_gpio_ok) + gpio_free(platform_data->wake_gpio); + +#if defined(CONFIG_OF) + if(platform_data) + kfree(platform_data); +#endif + return ret; +} + +static int cxd224x_remove(struct i2c_client *client) +{ + struct cxd224x_dev *cxd224x_dev; + + cxd224x_dev = i2c_get_clientdata(client); + + device_wakeup_disable(&client->dev); + + free_irq(client->irq, cxd224x_dev); + misc_deregister(&cxd224x_dev->cxd224x_device); + mutex_destroy(&cxd224x_dev->read_mutex); + if(cxd224x_dev->gpio) + { + gpio_free(cxd224x_dev->gpio->irq_gpio); + gpio_free(cxd224x_dev->gpio->wake_gpio); + +#if defined(CONFIG_NFC_CXD224X_VEN) || defined(CONFIG_NFC_CXD224X_VEN_MODULE) + gpio_free(cxd224x_dev->gpio->en_gpio); +#endif +#if defined(CONFIG_NFC_CXD224X_RST) || defined(CONFIG_NFC_CXD224X_RST_MODULE) + gpio_free(cxd224x_dev->gpio->rst_gpio); +#endif + +#if defined(CONFIG_OF) + + kfree(cxd224x_dev->gpio); +#endif + } + kfree(cxd224x_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int cxd224x_suspend(struct device *dev) +{ + struct cxd224x_dev *cxd224x_dev = dev_get_drvdata(dev); + + dev_info(dev, "%s\n", __func__); + + if (device_may_wakeup(&cxd224x_dev->client->dev)) { + int irq = gpio_to_irq(cxd224x_dev->gpio->irq_gpio); + enable_irq_wake(irq); + } + + cxd224x_pinctrl_select_state(dev, false); + + return 0; +} + +static int cxd224x_resume(struct device *dev) +{ + struct cxd224x_dev *cxd224x_dev = dev_get_drvdata(dev); + + dev_info(dev, "%s\n", __func__); + + if (device_may_wakeup(&cxd224x_dev->client->dev)) { + int irq = gpio_to_irq(cxd224x_dev->gpio->irq_gpio); + disable_irq_wake(irq); + } + + cxd224x_pinctrl_select_state(dev, true); + + return 0; +} + +static const struct dev_pm_ops cxd224x_pm_ops = { + .suspend = cxd224x_suspend, + .resume = cxd224x_resume, +}; +#endif + +static const struct i2c_device_id cxd224x_id[] = { + {"cxd224x-i2c", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cxd224x_id); + +static struct i2c_driver cxd224x_driver = { + .id_table = cxd224x_id, + .probe = cxd224x_probe, + .remove = cxd224x_remove, + .driver = { + .owner = THIS_MODULE, + .name = "cxd224x-i2c", + }, +}; + +static const struct platform_device_id cxd224x_pm_ops_id[] = { + { "cxd224x-pm-ops", 0 }, + { } +}; + +static const struct of_device_id cxd224x_pm_ops_match_table[] = { + { .compatible = "sony,cxd224x-pm-ops" }, + { } +}; + +static int cxd224x_pm_ops_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s, probing cxd224x PM OPS driver\n", __func__); + + if (!p_cxd224x_dev) + return -ENODEV; + + platform_set_drvdata(pdev, p_cxd224x_dev); + + dev_info(&pdev->dev, + "%s, probing cxd224x PM OPS driver successfully\n", __func__); + + return 0; +} + +static struct platform_driver cxd224x_pm_ops_driver = { + .id_table = cxd224x_pm_ops_id, + .probe = cxd224x_pm_ops_probe, + .driver = { + .name = "cxd224x-pm-ops", + .of_match_table = cxd224x_pm_ops_match_table, +#ifdef CONFIG_PM + .pm = &cxd224x_pm_ops, +#endif + }, +}; + +/* + * module load/unload record keeping + */ + +int cxd224x_dev_init(void) +{ + int ret; + + ret = i2c_add_driver(&cxd224x_driver); + if (ret) + goto exit; + + ret = platform_driver_register(&cxd224x_pm_ops_driver); + if (ret) + goto exit_del_i2c_driver; + + return 0; + +exit_del_i2c_driver: + i2c_del_driver(&cxd224x_driver); + +exit: + return ret; +} + +void cxd224x_dev_exit(void) +{ + if (p_cxd224x_dev) + p_cxd224x_dev = NULL; + + i2c_del_driver(&cxd224x_driver); + platform_driver_unregister(&cxd224x_pm_ops_driver); +} + +MODULE_AUTHOR("Sony"); +MODULE_DESCRIPTION("NFC cxd224x driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/carillon/cxd224x/cxd224x.h b/drivers/misc/carillon/cxd224x/cxd224x.h new file mode 100644 index 000000000000..6e2d171c8ce4 --- /dev/null +++ b/drivers/misc/carillon/cxd224x/cxd224x.h @@ -0,0 +1,47 @@ +/* + * cxd224x-i2c.c - cxd224x NFC driver + * + * Copyright (C) 2013- Sony Corporation. + * Copyright (C) 2012 Broadcom Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _CXD224X_H +#define _CXD224X_H + +#define CXDNFC_MAGIC 'S' +/* + * CXDNFC power control via ioctl + * CXDNFC_POWER_CTL(0): power off + * CXDNFC_POWER_CTL(1): power on + * CXDNFC_WAKE_CTL(0): PON HIGH (normal power mode) + * CXDNFC_WAKE_CTL(1): PON LOW (low power mode) + * CXDNFC_WAKE_RST(): assert XRST + */ +#define CXDNFC_POWER_CTL _IO(CXDNFC_MAGIC, 0x01) +#define CXDNFC_WAKE_CTL _IO(CXDNFC_MAGIC, 0x02) +#define CXDNFC_RST_CTL _IO(CXDNFC_MAGIC, 0x03) + +#define CXDNFC_RST_ACTIVE 1 /* ActiveHi = 1, ActiveLow = 0 */ + +struct cxd224x_platform_data { + unsigned int irq_gpio; + unsigned int en_gpio; + unsigned int wake_gpio; + unsigned int rst_gpio; +}; + +#endif diff --git a/drivers/misc/carillon/main_module.c b/drivers/misc/carillon/main_module.c new file mode 100644 index 000000000000..cb885f84d99d --- /dev/null +++ b/drivers/misc/carillon/main_module.c @@ -0,0 +1,54 @@ +/* main_module.c + * + * Copyright (C) 2017 Sony Mobile Communications Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + */ +#include +#include +#include + +extern int bd7602_dev_init(void); +extern void bd7602_dev_exit(void); +extern int cxd224x_dev_init(void); +extern void cxd224x_dev_exit(void); + +static int __init nfc_carillon_init(void) +{ + int ret; + + pr_info("%s\n", __func__); + + ret = bd7602_dev_init(); + if (ret) + goto exit; + + ret = cxd224x_dev_init(); + if (ret) + goto exit_del_bd7602_driver; + + return 0; + +exit_del_bd7602_driver: + bd7602_dev_exit(); + +exit: + return ret; +} +module_init(nfc_carillon_init); + +static void __exit nfc_carillon_exit(void) +{ + pr_info("%s\n", __func__); + + cxd224x_dev_exit(); + bd7602_dev_exit(); +} +module_exit(nfc_carillon_exit); + +MODULE_AUTHOR("Sony Mobile Communications Inc."); +MODULE_DESCRIPTION("NFC Carillon's component driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/cxd224x-i2c.c b/drivers/misc/cxd224x-i2c.c new file mode 100644 index 000000000000..cd784b2ce07a --- /dev/null +++ b/drivers/misc/cxd224x-i2c.c @@ -0,0 +1,739 @@ +/* drivers/misc/cxd224x-i2c.c + * + * Copyright (C) 2013 Sony Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + */ +/* + * Copyright (C) 2015 Sony Mobile Communications Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* do not change below */ +#define MAX_BUFFER_SIZE 780 + +/* Read data */ +#define PACKET_HEADER_SIZE_NCI (3) +#define PACKET_HEADER_SIZE_HCI (3) +#define PACKET_TYPE_NCI (16) +#define PACKET_TYPE_HCIEV (4) +#define MAX_PACKET_SIZE (PACKET_HEADER_SIZE_NCI + 255) + +#define CXD224X_WAKE_LOCK_TIMEOUT 3 +#define CXD224X_WAKE_LOCK_NAME CXD224X_DEVICE_NAME +#define CXD224X_WAKE_LOCK_TIMEOUT_LP 3 +#define CXD224X_WAKE_LOCK_NAME_LP "cxd224x-i2c-lp" + +#define CXD224X_PINCTRL_ACTIVE "felica_active" +#define CXD224X_PINCTRL_SUSPEND "felica_suspend" + +struct cxd224x_dev { + wait_queue_head_t read_wq; + struct mutex read_mutex; + struct i2c_client *client; + struct miscdevice cxd224x_device; + struct pinctrl *pinctrl; + struct pinctrl_state *gpio_state_active; + struct pinctrl_state *gpio_state_suspend; + unsigned int irq_gpio; + unsigned int wake_gpio; + bool irq_enabled; + spinlock_t irq_enabled_lock; + unsigned int count_irq; + struct wake_lock wakelock; + struct wake_lock wakelock_lp; +}; + +static struct cxd224x_dev *p_cxd224x_dev; + +static void cxd224x_init_stat(struct cxd224x_dev *cxd224x_dev) +{ + cxd224x_dev->count_irq = 0; +} + +static void cxd224x_disable_irq(struct cxd224x_dev *cxd224x_dev) +{ + unsigned long flags; + spin_lock_irqsave(&cxd224x_dev->irq_enabled_lock, flags); + if (cxd224x_dev->irq_enabled) { + disable_irq_nosync(cxd224x_dev->client->irq); + cxd224x_dev->irq_enabled = false; + } + spin_unlock_irqrestore(&cxd224x_dev->irq_enabled_lock, flags); +} + +static void cxd224x_enable_irq(struct cxd224x_dev *cxd224x_dev) +{ + unsigned long flags; + spin_lock_irqsave(&cxd224x_dev->irq_enabled_lock, flags); + if (!cxd224x_dev->irq_enabled) { + cxd224x_dev->irq_enabled = true; + enable_irq(cxd224x_dev->client->irq); + } + spin_unlock_irqrestore(&cxd224x_dev->irq_enabled_lock, flags); +} + +static irqreturn_t cxd224x_dev_irq_handler(int irq, void *dev_id) +{ + struct cxd224x_dev *cxd224x_dev = dev_id; + unsigned long flags; + + spin_lock_irqsave(&cxd224x_dev->irq_enabled_lock, flags); + cxd224x_dev->count_irq++; + spin_unlock_irqrestore(&cxd224x_dev->irq_enabled_lock, flags); + wake_up(&cxd224x_dev->read_wq); + + dev_info(&cxd224x_dev->client->dev, "%s\n", __func__); + + return IRQ_HANDLED; +} + +static unsigned int cxd224x_dev_poll(struct file *filp, poll_table *wait) +{ + struct cxd224x_dev *cxd224x_dev = filp->private_data; + unsigned int mask = 0; + unsigned long flags; + + poll_wait(filp, &cxd224x_dev->read_wq, wait); + + spin_lock_irqsave(&cxd224x_dev->irq_enabled_lock, flags); + if (cxd224x_dev->count_irq > 0) { + cxd224x_dev->count_irq--; + mask |= POLLIN | POLLRDNORM; + } + spin_unlock_irqrestore(&cxd224x_dev->irq_enabled_lock, flags); + + if (mask) + wake_lock_timeout(&cxd224x_dev->wakelock, + CXD224X_WAKE_LOCK_TIMEOUT * HZ); + return mask; +} + +static ssize_t cxd224x_dev_read(struct file *filp, char __user *buf, + size_t count, loff_t *offset) +{ + struct cxd224x_dev *cxd224x_dev = filp->private_data; + unsigned char tmp[MAX_BUFFER_SIZE]; + int total, len, ret; + + total = 0; + len = 0; + + if (count > MAX_BUFFER_SIZE) + count = MAX_BUFFER_SIZE; + + mutex_lock(&cxd224x_dev->read_mutex); + + ret = i2c_master_recv(cxd224x_dev->client, tmp, 3); + if (ret == 3 && (tmp[0] != 0xff)) { + total = ret; + + len = tmp[PACKET_HEADER_SIZE_NCI-1]; + + /* make sure full packet fits in the buffer */ + if (len > 0 && (len + total) <= count) { + /* read the remainder of the packet */ + ret = i2c_master_recv(cxd224x_dev->client, tmp+total, + len); + if (ret == len) + total += len; + } + } + + mutex_unlock(&cxd224x_dev->read_mutex); + + if (total > count || copy_to_user(buf, tmp, total)) { + dev_err(&cxd224x_dev->client->dev, + "failed to copy to user space, total = %d\n", total); + total = -EFAULT; + } + + return total; +} + +static ssize_t cxd224x_dev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offset) +{ + struct cxd224x_dev *cxd224x_dev = filp->private_data; + char tmp[MAX_BUFFER_SIZE]; + int ret; + + if (count > MAX_BUFFER_SIZE) { + dev_err(&cxd224x_dev->client->dev, "out of memory\n"); + return -ENOMEM; + } + + if (copy_from_user(tmp, buf, count)) { + dev_err(&cxd224x_dev->client->dev, + "failed to copy from user space\n"); + return -EFAULT; + } + + mutex_lock(&cxd224x_dev->read_mutex); + /* Write data */ + + ret = i2c_master_send(cxd224x_dev->client, tmp, count); + if (ret != count) { + dev_err(&cxd224x_dev->client->dev, + "failed to write %d\n", ret); + ret = -EIO; + } + mutex_unlock(&cxd224x_dev->read_mutex); + + return ret; +} + +static int cxd224x_dev_open(struct inode *inode, struct file *filp) +{ + int ret = 0; + + struct cxd224x_dev *cxd224x_dev = container_of(filp->private_data, + struct cxd224x_dev, + cxd224x_device); + filp->private_data = cxd224x_dev; + cxd224x_init_stat(cxd224x_dev); + cxd224x_enable_irq(cxd224x_dev); + dev_info(&cxd224x_dev->client->dev, + "%d,%d\n", imajor(inode), iminor(inode)); + + return ret; +} + +static const struct file_operations cxd224x_dev_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .poll = cxd224x_dev_poll, + .read = cxd224x_dev_read, + .write = cxd224x_dev_write, + .open = cxd224x_dev_open, +}; + +static ssize_t cxd224x_dev_wake_ctl_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u8 mode; + int ret, size; + struct cxd224x_dev *cxd224x_dev = dev_get_drvdata(dev); + + + if (!cxd224x_dev) { + ret = -ENODEV; + goto err; + } + ret = gpio_get_value_cansleep(cxd224x_dev->wake_gpio); + if (ret < 0) { + dev_err(dev, "%s: Unable to read WAKE GPIO\n", __func__); + goto err; + } + mode = !ret; + size = sizeof(mode); + memcpy(buf, &mode, size); + + return size; + +err: + return ret; +} + +static ssize_t cxd224x_dev_wake_ctl_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + u8 mode; + int ret, value; + struct cxd224x_dev *cxd224x_dev = dev_get_drvdata(dev); + + if (!cxd224x_dev) { + ret = -ENODEV; + goto err; + } + + if (sizeof(mode) != size) { + dev_err(dev, "%s: Invalid size %ld\n", __func__, size); + ret = -EINVAL; + goto err; + } + + memcpy(&mode, buf, size); + value = mode ? 0 : 1; + if (1 == value) { + wake_lock_timeout(&cxd224x_dev->wakelock_lp, CXD224X_WAKE_LOCK_TIMEOUT_LP*HZ); + gpio_set_value_cansleep(cxd224x_dev->wake_gpio, 1); + } else if (0 == value) { + gpio_set_value_cansleep(cxd224x_dev->wake_gpio, 0); + wake_unlock(&cxd224x_dev->wakelock_lp); + } else { + /* do nothing */ + } + return size; + +err: + return ret; +} + +static struct device_attribute cxd224x_sysfs_attrs[] = { + __ATTR(wake_ctl, S_IRUSR | S_IWUSR, cxd224x_dev_wake_ctl_show, + cxd224x_dev_wake_ctl_store), +}; + +static int cxd224x_pinctrl_init(struct device *dev, struct cxd224x_dev *cxd224x_dev) +{ + int ret = 0; + + cxd224x_dev->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR_OR_NULL(cxd224x_dev->pinctrl)) { + dev_err(dev, "error devm_pinctrl_get() failed err:%ld\n", + PTR_ERR(cxd224x_dev->pinctrl)); + ret = PTR_ERR(cxd224x_dev->pinctrl); + goto out; + } + + cxd224x_dev->gpio_state_active = pinctrl_lookup_state( + cxd224x_dev->pinctrl, CXD224X_PINCTRL_ACTIVE); + + if (IS_ERR_OR_NULL(cxd224x_dev->gpio_state_active)) { + ret = PTR_ERR(cxd224x_dev->gpio_state_active); + dev_info(dev, "note pinctrl_lookup_state(%s) err:%d\n", + CXD224X_PINCTRL_ACTIVE, ret); + goto out; + } + + cxd224x_dev->gpio_state_suspend = pinctrl_lookup_state( + cxd224x_dev->pinctrl, CXD224X_PINCTRL_SUSPEND); + + if (IS_ERR_OR_NULL(cxd224x_dev->gpio_state_suspend)) { + ret = PTR_ERR(cxd224x_dev->gpio_state_suspend); + dev_info(dev, "note pinctrl_lookup_state(%s) err:%d\n", + CXD224X_PINCTRL_ACTIVE, ret); + goto out; + } + +out: + return ret; +} + +static void cxd224x_pinctrl_select_state(struct device *dev, bool active) +{ + struct cxd224x_dev *cxd224x_dev = dev_get_drvdata(dev); + struct pinctrl_state *pins_state; + const char *pins_state_name; + + if (active) { + pins_state = cxd224x_dev->gpio_state_active; + pins_state_name = CXD224X_PINCTRL_ACTIVE; + } else { + pins_state = cxd224x_dev->gpio_state_suspend; + pins_state_name = CXD224X_PINCTRL_SUSPEND; + } + + if (!IS_ERR_OR_NULL(pins_state)) { + int ret = pinctrl_select_state(cxd224x_dev->pinctrl, + pins_state); + if (ret) + dev_err(dev, "error pinctrl_select_state(%s) err:%d\n", + pins_state_name, ret); + } else { + dev_err(dev, + "error pinctrl state-name:'%s' is not configured\n", + pins_state_name); + } +} + +static int cxd224x_create_sysfs_entries(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cxd224x_sysfs_attrs); i++) + if (device_create_file(dev, cxd224x_sysfs_attrs + i)) + goto err_create_file; + return 0; + +err_create_file: + for (i = i - 1; i >= 0; i--) + device_remove_file(dev, cxd224x_sysfs_attrs + i); + + dev_err(dev, "Unable to create sysfs interfaces\n"); + + return -EIO; +} + +static void cxd224x_remove_sysfs_entries(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cxd224x_sysfs_attrs); i++) + device_remove_file(dev, cxd224x_sysfs_attrs + i); +} + +static struct cxd224x_platform_data *cxd224x_dt_to_pdata(struct device *dev) +{ + int ret; + struct device_node *node = dev->of_node; + struct cxd224x_platform_data *pdata; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(dev, "unable to allocate memory for platform data\n"); + ret = -ENOMEM; + goto err; + } + pdata->irq_gpio = of_get_named_gpio(node, "cxd224x,irq_gpio", 0); + if (!gpio_is_valid(pdata->irq_gpio)) { + ret = -EINVAL; + dev_err(dev, + "failed to of_get_named_gpio(cxd224x,irq_gpio)\n"); + goto err; + } + pdata->wake_gpio = of_get_named_gpio(node, "cxd224x,wake_gpio", 0); + if (!gpio_is_valid(pdata->wake_gpio)) { + ret = -EINVAL; + dev_err(dev, + "failed to of_get_named_gpio(cxd224x,wake_gpio)\n"); + goto err; + } + + return pdata; +err: + if (pdata) + devm_kfree(dev, pdata); + + return ERR_PTR(ret); +} + +static int cxd224x_gpio_request(struct device *dev, + struct cxd224x_platform_data *pdata) +{ + int ret; + + ret = gpio_request(pdata->irq_gpio, "cxd224x_irq"); + if (ret) + goto err_irq; + ret = gpio_request(pdata->wake_gpio, "cxd224x_wake"); + if (ret) + goto err_wake; + + return 0; + +err_wake: + gpio_free(pdata->wake_gpio); +err_irq: + gpio_free(pdata->irq_gpio); + + dev_err(dev, "%s: failed to gpio request %d\n", __func__, ret); + return ret; +} + +static void cxd224x_gpio_free(struct device *dev, + struct cxd224x_platform_data *pdata) +{ + gpio_free(pdata->wake_gpio); + gpio_free(pdata->irq_gpio); +} + +static int cxd224x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct cxd224x_platform_data *platform_data; + struct cxd224x_dev *cxd224x_dev; + struct clk *felica_clk = NULL; + + platform_data = client->dev.platform_data; + + dev_info(&client->dev, "%s, probing cxd224x driver flags = %x\n", + __func__, client->flags); + + if (client->dev.of_node) { + platform_data = cxd224x_dt_to_pdata(&client->dev); + if (IS_ERR(platform_data)) { + ret = PTR_ERR(platform_data); + dev_err(&client->dev, "failed to dt_to_pdata\n"); + goto err_exit; + } + client->dev.platform_data = platform_data; + } + + if (platform_data == NULL) { + dev_err(&client->dev, "nfc probe fail\n"); + ret = -ENODEV; + goto err_exit; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "need I2C_FUNC_I2C\n"); + ret = -ENODEV; + goto err_exit; + } + + ret = cxd224x_gpio_request(&client->dev, platform_data); + if (ret) { + dev_err(&client->dev, "failed to gpio_request\n"); + goto err_exit; + } + + cxd224x_dev = kzalloc(sizeof(*cxd224x_dev), GFP_KERNEL); + if (cxd224x_dev == NULL) { + dev_err(&client->dev, + "failed to allocate memory for module data\n"); + ret = -ENOMEM; + goto err_kzalloc; + } + + cxd224x_dev->irq_gpio = platform_data->irq_gpio; + cxd224x_dev->wake_gpio = platform_data->wake_gpio; + cxd224x_dev->client = client; + wake_lock_init(&cxd224x_dev->wakelock, WAKE_LOCK_SUSPEND, + CXD224X_WAKE_LOCK_NAME); + wake_lock_init(&cxd224x_dev->wakelock_lp, WAKE_LOCK_SUSPEND, CXD224X_WAKE_LOCK_NAME_LP); + + /* init mutex and queues */ + init_waitqueue_head(&cxd224x_dev->read_wq); + mutex_init(&cxd224x_dev->read_mutex); + spin_lock_init(&cxd224x_dev->irq_enabled_lock); + + ret = cxd224x_pinctrl_init(&client->dev, cxd224x_dev); + if (ret) { + dev_err(&client->dev, "pinctrl_init failed\n"); + goto err_pinctrl_init; + } + + cxd224x_dev->cxd224x_device.minor = MISC_DYNAMIC_MINOR; + cxd224x_dev->cxd224x_device.name = CXD224X_DEVICE_NAME; + cxd224x_dev->cxd224x_device.fops = &cxd224x_dev_fops; + + ret = misc_register(&cxd224x_dev->cxd224x_device); + if (ret) { + dev_err(&client->dev, "misc_register failed\n"); + goto err_misc_register; + } + + felica_clk = clk_get(&client->dev, "felica_clk"); + if (IS_ERR(felica_clk)) { + dev_err(&client->dev, "Couldn't get felica_clk\n"); + goto err_clk; + } + ret = clk_prepare_enable(felica_clk); + if (ret) { + dev_err(&client->dev, "failed to enable felica_clk\n"); + goto err_clk_enable; + } + + ret = cxd224x_create_sysfs_entries(&client->dev); + if (ret) { + dev_err(&client->dev, "create_sysfs_entries failed\n"); + goto err_create_sysfs_entries; + } + + /* request irq. the irq is set whenever the chip has data available + * for reading. it is cleared when all data has been read. + */ + dev_info(&client->dev, "requesting IRQ %d\n", client->irq); + cxd224x_dev->irq_enabled = true; + ret = request_threaded_irq(client->irq, NULL, cxd224x_dev_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + client->name, cxd224x_dev); + if (ret) { + dev_err(&client->dev, "request_irq failed\n"); + goto err_request_irq_failed; + } + + cxd224x_disable_irq(cxd224x_dev); + i2c_set_clientdata(client, cxd224x_dev); + cxd224x_pinctrl_select_state(&client->dev, true); + device_init_wakeup(&client->dev, 1); + p_cxd224x_dev = cxd224x_dev; + + dev_info(&client->dev, + "%s, probing cxd224x driver exited successfully\n", + __func__); + + return 0; + +err_request_irq_failed: + cxd224x_remove_sysfs_entries(&client->dev); +err_create_sysfs_entries: + misc_deregister(&cxd224x_dev->cxd224x_device); +err_clk_enable: + clk_put(felica_clk); +err_clk: +err_misc_register: +err_pinctrl_init: + mutex_destroy(&cxd224x_dev->read_mutex); + wake_lock_destroy(&cxd224x_dev->wakelock); + kzfree(cxd224x_dev); +err_kzalloc: + cxd224x_gpio_free(&client->dev, platform_data); +err_exit: + return ret; +} + +static int cxd224x_remove(struct i2c_client *client) +{ + struct cxd224x_dev *cxd224x_dev = i2c_get_clientdata(client); + + free_irq(client->irq, cxd224x_dev); + cxd224x_remove_sysfs_entries(&client->dev); + misc_deregister(&cxd224x_dev->cxd224x_device); + mutex_destroy(&cxd224x_dev->read_mutex); + wake_lock_destroy(&cxd224x_dev->wakelock); + wake_lock_destroy(&cxd224x_dev->wakelock_lp); + i2c_set_clientdata(client, NULL); + kzfree(cxd224x_dev); + cxd224x_gpio_free(&client->dev, client->dev.platform_data); + + return 0; +} + +#ifdef CONFIG_PM +static int cxd224x_suspend(struct device *dev) +{ + struct cxd224x_dev *cxd224x_dev = dev_get_drvdata(dev); + dev_info(dev, "%s\n", __func__); + + if (device_may_wakeup(&cxd224x_dev->client->dev)) + enable_irq_wake(cxd224x_dev->client->irq); + + cxd224x_pinctrl_select_state(dev, false); + + return 0; +} + +static int cxd224x_resume(struct device *dev) +{ + struct cxd224x_dev *cxd224x_dev = dev_get_drvdata(dev); + dev_info(dev, "%s\n", __func__); + + if (device_may_wakeup(&cxd224x_dev->client->dev)) + disable_irq_wake(cxd224x_dev->client->irq); + + cxd224x_pinctrl_select_state(dev, true); + + return 0; +} + +static const struct dev_pm_ops cxd224x_pm_ops = { + .suspend = cxd224x_suspend, + .resume = cxd224x_resume, +}; +#endif + +static const struct i2c_device_id cxd224x_id[] = { + { CXD224X_DEVICE_NAME, 0 }, + { } +}; + +static struct of_device_id cxd224x_match_table[] = { + { .compatible = "sony,cxd224x", }, + { }, +}; + +static struct i2c_driver cxd224x_driver = { + .id_table = cxd224x_id, + .probe = cxd224x_probe, + .remove = cxd224x_remove, + .driver = { + .owner = THIS_MODULE, + .name = CXD224X_DEVICE_NAME, + .of_match_table = cxd224x_match_table, + }, +}; + +static const struct platform_device_id cxd224x_pm_ops_id[] = { + { CXD224X_PM_OPS_DEVICE_NAME, 0 }, + { } +}; + +static const struct of_device_id cxd224x_pm_ops_match_table[] = { + { .compatible = "sony,cxd224x-pm-ops" }, + { } +}; + +static int cxd224x_pm_ops_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s, probing cxd224x PM OPS driver\n", __func__); + + if (!p_cxd224x_dev) + return -ENODEV; + + platform_set_drvdata(pdev, p_cxd224x_dev); + + dev_info(&pdev->dev, + "%s, probing cxd224x PM OPS driver successfully\n", __func__); + + return 0; +} + +static struct platform_driver cxd224x_pm_ops_driver = { + .id_table = cxd224x_pm_ops_id, + .probe = cxd224x_pm_ops_probe, + .driver = { + .name = CXD224X_PM_OPS_DEVICE_NAME, + .of_match_table = cxd224x_pm_ops_match_table, +#ifdef CONFIG_PM + .pm = &cxd224x_pm_ops, +#endif + }, +}; + +static int __init cxd224x_dev_init(void) +{ + int ret; + + ret = i2c_add_driver(&cxd224x_driver); + + if (ret) + goto exit; + + ret = platform_driver_register(&cxd224x_pm_ops_driver); + + if (ret) + goto exit_del_i2c_driver; + + return 0; + +exit_del_i2c_driver: + i2c_del_driver(&cxd224x_driver); + +exit: + return ret; +} +module_init(cxd224x_dev_init); + +static void __exit cxd224x_dev_exit(void) +{ + if (p_cxd224x_dev) + p_cxd224x_dev = NULL; + + i2c_del_driver(&cxd224x_driver); + platform_driver_unregister(&cxd224x_pm_ops_driver); +} +module_exit(cxd224x_dev_exit); + +MODULE_AUTHOR("Sony"); +MODULE_DESCRIPTION("NFC cxd224x driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/cxd224x.h b/include/linux/cxd224x.h new file mode 100644 index 000000000000..5756a369b922 --- /dev/null +++ b/include/linux/cxd224x.h @@ -0,0 +1,29 @@ +/* include/linux/cxd224x.h + * + * Copyright (C) 2012 Sony Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + */ +/* + * Copyright (C) 2015 Sony Mobile Communications Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + */ + +#ifndef _CXD224X_H +#define _CXD224X_H + +#define CXD224X_DEVICE_NAME "cxd224x-i2c" +#define CXD224X_PM_OPS_DEVICE_NAME "cxd224x-pm-ops" + +struct cxd224x_platform_data { + unsigned int irq_gpio; + unsigned int wake_gpio; +}; + +#endif