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 = µread_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(µread_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(µread_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