[PATCH] NFC: Driver for Inside Secure MicroRead NFC chip

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add new driver for MicroRead NFC chip connected to i2c bus.

See Documentation/nfc/nfc-microread.txt.

Signed-off-by: Waldemar Rymarkiewicz <waldemar.rymarkiewicz@xxxxxxxxx>
---
 Documentation/nfc/nfc-microread.txt |   46 ++++
 drivers/nfc/Kconfig                 |   10 +
 drivers/nfc/Makefile                |    1 +
 drivers/nfc/microread.c             |  486 +++++++++++++++++++++++++++++++++++
 include/linux/nfc/microread.h       |   34 +++
 5 files changed, 577 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/nfc/nfc-microread.txt
 create mode 100644 drivers/nfc/microread.c
 create mode 100644 include/linux/nfc/microread.h

diff --git a/Documentation/nfc/nfc-microread.txt b/Documentation/nfc/nfc-microread.txt
new file mode 100644
index 0000000..e15c49c
--- /dev/null
+++ b/Documentation/nfc/nfc-microread.txt
@@ -0,0 +1,46 @@
+Kernel driver for Inside Secure MicroRead Near Field Communication chip
+
+General
+-------
+
+microread is the RF chip that uses Near Field Communication (NFC) for proximity
+transactions and interactions.
+
+Please visit Inside Secure webside for microread specification:
+http://www.insidesecure.com/eng/Products/NFC-Products/MicroRead
+
+
+The Driver
+----------
+
+The microread driver can be found under drivers/nfc/microread.c and it's
+compiled as a module named "microread".
+
+The driver supports i2c interface only.
+
+The userspace application can use /dev/microread device to control the chip by
+HCI messages. In a typical scenario application will open() /dev/microread,
+reset the chip useing ioctl() interface then poll() the device to check if
+writing/reaing is possible.Finally, will read/write data (HCI) from/to the chip.
+
+Note that only one application can use the /dev/microread at the time.
+
+The driver waits for microread chip interrupt which occures if there are some
+data to be read. Then, poll() will indicate that fact to the userspace.
+
+The application can use ioctl(MICROREAD_IOC_RESET)to reset the hardware.
+
+
+Platform Data
+-------------
+
+The below platform data should be set when configuring board.
+
+struct microread_nfc_platform_data {
+	unsigned int rst_gpio;
+	unsigned int irq_gpio;
+	unsigned int ioh_gpio;
+	int (*request_hardware) (struct i2c_client *client);
+	void (*release_hardware) (void);
+};
+
diff --git a/drivers/nfc/Kconfig b/drivers/nfc/Kconfig
index ea15800..a805615 100644
--- a/drivers/nfc/Kconfig
+++ b/drivers/nfc/Kconfig
@@ -26,5 +26,15 @@ config PN544_NFC
 	  To compile this driver as a module, choose m here. The module will
 	  be called pn544.
 
+config MICROREAD_NFC
+	tristate "MICROREAD NFC driver"
+	depends on I2C
+	default n
+	---help---
+	  Say yes if you want Inside Secure Microread Near Field Communication
+	  driver. This is for i2c connected version. If unsure, say N here.
+
+	  To compile this driver as a module, choose m here. The module will
+	  be called microread.
 
 endif # NFC_DEVICES
diff --git a/drivers/nfc/Makefile b/drivers/nfc/Makefile
index a4efb16..974f5cb 100644
--- a/drivers/nfc/Makefile
+++ b/drivers/nfc/Makefile
@@ -3,3 +3,4 @@
 #
 
 obj-$(CONFIG_PN544_NFC)		+= pn544.o
+obj-$(CONFIG_MICROREAD_NFC)	+= microread.o
diff --git a/drivers/nfc/microread.c b/drivers/nfc/microread.c
new file mode 100644
index 0000000..4393033
--- /dev/null
+++ b/drivers/nfc/microread.c
@@ -0,0 +1,486 @@
+/*
+ *  Driver for Microread NFC chip
+ *
+ *  Copyright (C) 2011 Tieto Poland
+ *
+ *  Author: Waldemar Rymarkiewicz <waldemar.rymarkiewicz@xxxxxxxxx>
+ *
+ *  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
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/miscdevice.h>
+#include <linux/nfc/microread.h>
+#include <linux/uaccess.h>
+#include <linux/pm.h>
+
+#define MICROREAD_DEV_NAME	"microread"
+#define MICROREAD_MAX_BUF_SIZE	0x20
+
+#define MICROREAD_STATE_BUSY	0x00
+#define MICROREAD_STATE_READY	0x01
+
+struct microread_info {
+	struct i2c_client *i2c_dev;
+	struct miscdevice mdev;
+
+	u8 state;
+	u8 irq_state;
+	int irq;
+	u16 buflen;
+	u8 *buf;
+	wait_queue_head_t rx_waitq;
+	struct mutex rx_mutex;
+	struct mutex mutex;
+};
+
+static void microread_reset_hw(struct microread_nfc_platform_data *pdata)
+{
+	gpio_set_value(pdata->rst_gpio, 1);
+	usleep_range(1000, 2000);
+	gpio_set_value(pdata->rst_gpio, 0);
+	usleep_range(5000, 10000);
+}
+
+static u8 calc_lrc(u8 *buf, int len)
+{
+	int i;
+	u8 lrc = 0;
+	for (i = 0; i < len; i++)
+		lrc = lrc ^ buf[i];
+	return lrc;
+}
+
+static inline int microread_is_busy(struct microread_info *info)
+{
+	return (info->state == MICROREAD_STATE_BUSY);
+}
+
+static int microread_open(struct inode *inode, struct file *file)
+{
+	struct microread_info *info = container_of(file->private_data,
+					struct microread_info, mdev);
+	struct i2c_client *client = info->i2c_dev;
+	int ret = 0;
+
+	dev_vdbg(&client->dev, "%s: info: %p", __func__, info);
+
+	mutex_lock(&info->mutex);
+
+	if (microread_is_busy(info)) {
+		dev_err(&client->dev, "%s: info %p: device is busy.", __func__,
+				info);
+		ret = -EBUSY;
+		goto done;
+	}
+
+	info->state = MICROREAD_STATE_BUSY;
+done:
+	mutex_unlock(&info->mutex);
+	return ret;
+}
+
+static int microread_release(struct inode *inode, struct file *file)
+{
+	struct microread_info *info = container_of(file->private_data,
+					struct microread_info, mdev);
+	struct i2c_client *client = info->i2c_dev;
+
+	dev_vdbg(&client->dev, "%s: info: %p, client %p\n", __func__, info,
+					client);
+
+	mutex_lock(&info->mutex);
+	info->state = MICROREAD_STATE_READY;
+	mutex_unlock(&info->mutex);
+
+	return 0;
+}
+
+ssize_t microread_read(struct file *file, char __user *buf, size_t count,
+								loff_t *f_pos)
+{
+	struct microread_info *info = container_of(file->private_data,
+					struct microread_info, mdev);
+	struct i2c_client *client = info->i2c_dev;
+	struct microread_nfc_platform_data *pdata =
+				dev_get_platdata(&client->dev);
+	int ret;
+	u8 len, lrc;
+
+	dev_dbg(&client->dev, "%s: info: %p, size: %d (bytes).", __func__,
+						info, count);
+	mutex_lock(&info->mutex);
+
+	if (!info->irq_state && !gpio_get_value(pdata->irq_gpio)) {
+		if (file->f_flags & O_NONBLOCK) {
+			ret = -EAGAIN;
+			goto done;
+		}
+
+		if (wait_event_interruptible(info->rx_waitq,
+				(info->irq_state == 1))) {
+			ret = -EINTR;
+			goto done;
+		}
+	}
+
+	if (count > info->buflen) {
+		dev_err(&client->dev, "%s: no enough space in read buffer.",
+				__func__);
+		ret = -ENOMEM;
+		goto done;
+	}
+
+	mutex_lock(&info->rx_mutex);
+	ret = i2c_master_recv(client, info->buf, info->buflen);
+	info->irq_state = 0;
+	mutex_unlock(&info->rx_mutex);
+
+	if (ret < 0) {
+		dev_err(&client->dev, "%s: i2c read (data) failed (err %d).",
+				__func__, ret);
+		ret = -EREMOTEIO;
+		goto done;
+	}
+
+#ifdef VERBOSE_DEBUG
+		print_hex_dump(KERN_DEBUG, "rx: ", DUMP_PREFIX_NONE,
+				16, 1, info->buf, ret, false);
+#endif
+	len = info->buf[0];
+
+	lrc = calc_lrc(info->buf, len + 1);
+	if (lrc != info->buf[len + 1]) {
+		dev_err(&client->dev, "%s: incorrect i2c frame.", __func__);
+		ret = -EFAULT;
+		goto done;
+	}
+
+	ret = len + 2;
+
+	if (copy_to_user(buf, info->buf, len + 2)) {
+		dev_err(&client->dev, "%s: error copying to user.", __func__);
+		ret = -EFAULT;
+		goto done;
+	}
+done:
+	mutex_unlock(&info->mutex);
+
+	return ret;
+}
+
+ssize_t microread_write(struct file *file, const char __user *buf,
+			size_t count, loff_t *f_pos)
+{
+	struct microread_info *info = container_of(file->private_data,
+					struct microread_info, mdev);
+	struct i2c_client *client = info->i2c_dev;
+	int ret;
+	u16 len;
+
+	dev_dbg(&client->dev, "%s: info: %p, size %d (bytes).", __func__,
+					info, count);
+
+	if (count > info->buflen) {
+		dev_err(&client->dev, "%s: no enought space in TX buffer.",
+				__func__);
+		return -EINVAL;
+	}
+
+	len = min_t(u16, count, info->buflen);
+
+	mutex_lock(&info->mutex);
+	if (copy_from_user(info->buf, buf, len)) {
+		dev_err(&client->dev, "%s: error copying from user.",
+				__func__);
+		ret = -EFAULT;
+		goto done;
+	}
+
+#ifdef VERBOSE_DEBUG
+	print_hex_dump(KERN_DEBUG, "tx: ", DUMP_PREFIX_NONE, 16, 1, info->buf,
+			len, false);
+#endif
+	ret = i2c_master_send(client, info->buf, len);
+	if (ret < 0)
+		dev_err(&client->dev, "%s: i2c write failed (err %d).",
+			__func__, ret);
+	usleep_range(1000, 10000);
+done:
+	mutex_unlock(&info->mutex);
+	return ret;
+
+}
+
+unsigned int microread_poll(struct file *file, poll_table *wait)
+{
+	struct microread_info *info = container_of(file->private_data,
+					struct microread_info, mdev);
+	struct i2c_client *client = info->i2c_dev;
+	int ret = (POLLOUT | POLLWRNORM);
+
+	dev_vdbg(&client->dev, "%s: info: %p client %p", __func__, info,
+			client);
+
+	mutex_lock(&info->mutex);
+	poll_wait(file, &info->rx_waitq, wait);
+
+	mutex_lock(&info->rx_mutex);
+	if (info->irq_state)
+		ret |= (POLLIN | POLLRDNORM);
+	mutex_unlock(&info->rx_mutex);
+	mutex_unlock(&info->mutex);
+
+	return ret;
+}
+
+long microread_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct microread_info *info = container_of(file->private_data,
+					struct microread_info, mdev);
+	struct i2c_client *client = info->i2c_dev;
+	struct microread_nfc_platform_data *pdata =
+				dev_get_platdata(&client->dev);
+	int ret = 0;
+
+	dev_dbg(&client->dev, "%s: info: %p cmd %d", __func__, info, cmd);
+
+	mutex_lock(&info->mutex);
+
+	switch (cmd) {
+	case MICROREAD_IOC_CONFIGURE:
+	case MICROREAD_IOC_CONNECT:
+		goto done;
+
+	case MICROREAD_IOC_RESET:
+		microread_reset_hw(pdata);
+		goto done;
+
+	default:
+		dev_err(&client->dev, "%s; not supported ioctl 0x%x", __func__,
+			cmd);
+		ret = -ENOIOCTLCMD;
+		goto done;
+	}
+
+done:
+	mutex_unlock(&info->mutex);
+	return ret;
+}
+
+const struct file_operations microread_fops = {
+	.owner		= THIS_MODULE,
+	.open		= microread_open,
+	.release	= microread_release,
+	.read		= microread_read,
+	.write		= microread_write,
+	.poll		= microread_poll,
+	.unlocked_ioctl	= microread_ioctl,
+};
+
+static irqreturn_t microread_irq(int irq, void *dev)
+{
+	struct microread_info *info = dev;
+	struct i2c_client *client = info->i2c_dev;
+
+	dev_dbg(&client->dev, "irq: info %p client %p ", info,	client);
+
+	if (irq != client->irq)
+		return IRQ_NONE;
+
+	mutex_lock(&info->rx_mutex);
+	info->irq_state = 1;
+	mutex_unlock(&info->rx_mutex);
+
+	wake_up_interruptible(&info->rx_waitq);
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit microread_probe(struct i2c_client *client,
+				 const struct i2c_device_id *id)
+{
+	struct microread_info *info;
+	struct microread_nfc_platform_data *pdata =
+				dev_get_platdata(&client->dev);
+	int ret = 0;
+
+	dev_dbg(&client->dev, "%s: client %p", __func__, client);
+
+	if (!pdata) {
+		dev_err(&client->dev, "%s: client %p: missing platform data.",
+				__func__, client);
+		return -EINVAL;
+	}
+
+	if (!pdata->request_hardware) {
+		dev_err(&client->dev, "%s: client %p: no request_hardware()",
+				__func__, client);
+		return -EINVAL;
+	}
+
+	info = kzalloc(sizeof(struct microread_info), GFP_KERNEL);
+	if (!info) {
+		dev_err(&client->dev, "%s: can't allocate microread_info.",
+				__func__);
+		return -ENOMEM;
+	}
+
+	info->buflen = (u16) MICROREAD_MAX_BUF_SIZE;
+	info->buf = kzalloc(info->buflen, GFP_KERNEL);
+	if (!info->buf) {
+		dev_err(&client->dev, "%s: can't allocate buf. (len %d)",
+			__func__, info->buflen);
+		ret = -ENOMEM;
+		goto free_info;
+	}
+
+	mutex_init(&info->mutex);
+	mutex_init(&info->rx_mutex);
+	init_waitqueue_head(&info->rx_waitq);
+	i2c_set_clientdata(client, info);
+	info->i2c_dev = client;
+	info->irq_state = 0;
+	info->state = MICROREAD_STATE_READY;
+
+	ret = pdata->request_hardware(client);
+	if (ret) {
+		dev_err(&client->dev, "%s: requesting hardware failed (ret %d)",
+				__func__, ret);
+		goto free_buf;
+	}
+
+	ret = request_threaded_irq(client->irq, NULL, microread_irq,
+		IRQF_SHARED|IRQF_TRIGGER_RISING, MICROREAD_DRIVER_NAME, info);
+	if (ret) {
+		dev_err(&client->dev, "%s: can't request irq. (ret %d, irq %d)",
+			__func__, ret, client->irq);
+		goto free_hardware;
+	}
+
+	info->mdev.minor = MISC_DYNAMIC_MINOR;
+	info->mdev.name = MICROREAD_DEV_NAME;
+	info->mdev.fops = &microread_fops;
+	info->mdev.parent = &client->dev;
+
+	ret = misc_register(&info->mdev);
+	if (ret < 0) {
+		dev_err(&client->dev, "%s: register chr dev failed (ret %d)",
+			__func__, ret);
+			goto free_irq;
+	}
+
+	dev_info(&client->dev, "Probed.[/dev/%s]", MICROREAD_DEV_NAME);
+	return 0;
+
+free_irq:
+	free_irq(client->irq, info);
+free_hardware:
+	pdata->release_hardware();
+free_buf:
+	kfree(info->buf);
+free_info:
+	kfree(info);
+
+	dev_info(&client->dev, "Not probed.");
+	return ret;
+}
+
+static __devexit int microread_remove(struct i2c_client *client)
+{
+	struct microread_info *info = i2c_get_clientdata(client);
+	struct microread_nfc_platform_data *pdata =
+				dev_get_platdata(&client->dev);
+
+	dev_dbg(&client->dev, "%s", __func__);
+
+	misc_deregister(&info->mdev);
+	free_irq(client->irq, info);
+
+	if (pdata->release_hardware)
+		pdata->release_hardware();
+
+	kfree(info->buf);
+	kfree(info);
+
+	dev_info(&client->dev, "%s: Removed.", __func__);
+	return 0;
+}
+
+static int microread_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	return -ENOSYS;
+}
+
+static int microread_resume(struct i2c_client *client)
+{
+	return -ENOSYS;
+}
+
+static struct i2c_device_id microread_id[] = {
+	{ MICROREAD_DRIVER_NAME, 0},
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, microread_id);
+
+static struct i2c_driver microread_driver = {
+	.driver = {
+		.name = MICROREAD_DRIVER_NAME,
+	},
+	.probe		= microread_probe,
+	.remove		= __devexit_p(microread_remove),
+	.suspend	= microread_suspend,
+	.resume		= microread_resume,
+	.id_table	= microread_id,
+};
+
+static int __init microread_init(void)
+{
+	int ret;
+
+	pr_debug(MICROREAD_DRIVER_NAME ": %s", __func__);
+
+	ret = i2c_add_driver(&microread_driver);
+	if (ret) {
+		pr_err(MICROREAD_DRIVER_NAME ": driver registration failed");
+		return ret;
+	}
+	pr_info(MICROREAD_DRIVER_NAME ": Loaded.");
+	return 0;
+}
+
+static void __exit microread_exit(void)
+{
+	pr_debug(MICROREAD_DRIVER_NAME ": %s", __func__);
+	i2c_del_driver(&microread_driver);
+	pr_info(MICROREAD_DRIVER_NAME ": Unloaded");
+}
+
+module_init(microread_init);
+module_exit(microread_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Waldemar Rymarkiewicz <waldemar.rymarkiewicz@xxxxxxxxx>");
+MODULE_DESCRIPTION("Driver for Microread NFC chip");
+
diff --git a/include/linux/nfc/microread.h b/include/linux/nfc/microread.h
new file mode 100644
index 0000000..1adbf93
--- /dev/null
+++ b/include/linux/nfc/microread.h
@@ -0,0 +1,34 @@
+#ifndef _MICROREAD_H
+#define _MICROREAD_H
+
+#include <linux/i2c.h>
+
+#define MICROREAD_DRIVER_NAME	"microread"
+
+#define MICROREAD_IOC_MAGIC	'o'
+#define MICROREAD_IOC_MAX_CONFIG_LENGTH	16
+
+struct microread_ioc_configure {
+	int length;
+	unsigned char buffer[MICROREAD_IOC_MAX_CONFIG_LENGTH];
+};
+
+/* ioctl cmds */
+#define MICROREAD_IOC_CONFIGURE	_IOW(MICROREAD_IOC_MAGIC, 0, \
+					struct microread_ioc_configure)
+#define MICROREAD_IOC_CONNECT	_IO(MICROREAD_IOC_MAGIC, 1)
+#define MICROREAD_IOC_RESET	_IO(MICROREAD_IOC_MAGIC, 2)
+
+#ifdef __KERNEL__
+/* board config platform data for microread */
+struct microread_nfc_platform_data {
+	unsigned int rst_gpio;
+	unsigned int irq_gpio;
+	unsigned int ioh_gpio;
+	int (*request_hardware) (struct i2c_client *client);
+	void (*release_hardware) (void);
+};
+#endif /* __KERNEL__ */
+
+#endif /* _MICROREAD_H */
+
-- 
1.7.1

--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux GPIO]     [Linux SPI]     [Linux Hardward Monitoring]     [LM Sensors]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux