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

From: Waldemar Rymarkiewicz
Date: Thu Mar 10 2011 - 08:17:47 EST


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-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/