diff options
author | Vincent Cuissard <cuissard@marvell.com> | 2015-10-26 10:27:41 +0100 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2015-10-27 04:21:14 +0100 |
commit | b5b3e23e4cace008e1a30e8614a484d14dfd07a1 (patch) | |
tree | 451bd798e76313fb43dc654530374d4b3c6e3d5c /drivers/nfc/nfcmrvl | |
parent | NFC: nfcmrvl: configure head/tail room values per low level drivers (diff) | |
download | linux-b5b3e23e4cace008e1a30e8614a484d14dfd07a1.tar.xz linux-b5b3e23e4cace008e1a30e8614a484d14dfd07a1.zip |
NFC: nfcmrvl: add i2c driver
This driver adds the support of I2C-based Marvell NFC controller.
Signed-off-by: Vincent Cuissard <cuissard@marvell.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers/nfc/nfcmrvl')
-rw-r--r-- | drivers/nfc/nfcmrvl/Kconfig | 12 | ||||
-rw-r--r-- | drivers/nfc/nfcmrvl/Makefile | 3 | ||||
-rw-r--r-- | drivers/nfc/nfcmrvl/i2c.c | 290 | ||||
-rw-r--r-- | drivers/nfc/nfcmrvl/main.c | 9 | ||||
-rw-r--r-- | drivers/nfc/nfcmrvl/nfcmrvl.h | 1 |
5 files changed, 311 insertions, 4 deletions
diff --git a/drivers/nfc/nfcmrvl/Kconfig b/drivers/nfc/nfcmrvl/Kconfig index 19ac492bc25f..e18a979bb6d3 100644 --- a/drivers/nfc/nfcmrvl/Kconfig +++ b/drivers/nfc/nfcmrvl/Kconfig @@ -30,3 +30,15 @@ config NFC_MRVL_UART Say Y here to compile support for Marvell NFC-over-UART driver into the kernel or say M to compile it as module. + +config NFC_MRVL_I2C + tristate "Marvell NFC-over-I2C driver" + depends on NFC_MRVL && I2C + help + Marvell NFC-over-I2C driver. + + This driver provides support for Marvell NFC-over-I2C devices. + + Say Y here to compile support for Marvell NFC-over-I2C driver + into the kernel or say M to compile it as module. + diff --git a/drivers/nfc/nfcmrvl/Makefile b/drivers/nfc/nfcmrvl/Makefile index 4554ee8e3680..895866a3ebc6 100644 --- a/drivers/nfc/nfcmrvl/Makefile +++ b/drivers/nfc/nfcmrvl/Makefile @@ -10,3 +10,6 @@ obj-$(CONFIG_NFC_MRVL_USB) += nfcmrvl_usb.o nfcmrvl_uart-y += uart.o obj-$(CONFIG_NFC_MRVL_UART) += nfcmrvl_uart.o + +nfcmrvl_i2c-y += i2c.o +obj-$(CONFIG_NFC_MRVL_I2C) += nfcmrvl_i2c.o diff --git a/drivers/nfc/nfcmrvl/i2c.c b/drivers/nfc/nfcmrvl/i2c.c new file mode 100644 index 000000000000..7a44025bdaad --- /dev/null +++ b/drivers/nfc/nfcmrvl/i2c.c @@ -0,0 +1,290 @@ +/** + * Marvell NFC-over-I2C driver: I2C interface related functions + * + * Copyright (C) 2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available on the worldwide web at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + **/ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/pm_runtime.h> +#include <linux/nfc.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/of_irq.h> +#include <linux/of_gpio.h> +#include <net/nfc/nci.h> +#include <net/nfc/nci_core.h> +#include "nfcmrvl.h" + +struct nfcmrvl_i2c_drv_data { + unsigned long flags; + struct device *dev; + struct i2c_client *i2c; + struct nfcmrvl_private *priv; +}; + +static int nfcmrvl_i2c_read(struct nfcmrvl_i2c_drv_data *drv_data, + struct sk_buff **skb) +{ + int ret; + struct nci_ctrl_hdr nci_hdr; + + /* Read NCI header to know the payload size */ + ret = i2c_master_recv(drv_data->i2c, (u8 *)&nci_hdr, NCI_CTRL_HDR_SIZE); + if (ret != NCI_CTRL_HDR_SIZE) { + nfc_err(&drv_data->i2c->dev, "cannot read NCI header\n"); + return -EBADMSG; + } + + if (nci_hdr.plen > NCI_MAX_PAYLOAD_SIZE) { + nfc_err(&drv_data->i2c->dev, "invalid packet payload size\n"); + return -EBADMSG; + } + + *skb = nci_skb_alloc(drv_data->priv->ndev, + nci_hdr.plen + NCI_CTRL_HDR_SIZE, GFP_KERNEL); + if (!*skb) + return -ENOMEM; + + /* Copy NCI header into the SKB */ + memcpy(skb_put(*skb, NCI_CTRL_HDR_SIZE), &nci_hdr, NCI_CTRL_HDR_SIZE); + + if (nci_hdr.plen) { + /* Read the NCI payload */ + ret = i2c_master_recv(drv_data->i2c, + skb_put(*skb, nci_hdr.plen), + nci_hdr.plen); + + if (ret != nci_hdr.plen) { + nfc_err(&drv_data->i2c->dev, + "Invalid frame payload length: %u (expected %u)\n", + ret, nci_hdr.plen); + kfree_skb(*skb); + return -EBADMSG; + } + } + + return 0; +} + +static irqreturn_t nfcmrvl_i2c_int_irq_thread_fn(int irq, void *drv_data_ptr) +{ + struct nfcmrvl_i2c_drv_data *drv_data = drv_data_ptr; + struct sk_buff *skb = NULL; + int ret; + + if (!drv_data->priv) + return IRQ_HANDLED; + + if (test_bit(NFCMRVL_PHY_ERROR, &drv_data->priv->flags)) + return IRQ_HANDLED; + + ret = nfcmrvl_i2c_read(drv_data, &skb); + + switch (ret) { + case -EREMOTEIO: + set_bit(NFCMRVL_PHY_ERROR, &drv_data->priv->flags); + break; + case -ENOMEM: + case -EBADMSG: + nfc_err(&drv_data->i2c->dev, "read failed %d\n", ret); + break; + default: + if (nfcmrvl_nci_recv_frame(drv_data->priv, skb) < 0) + nfc_err(&drv_data->i2c->dev, "corrupted RX packet\n"); + break; + } + return IRQ_HANDLED; +} + +static int nfcmrvl_i2c_nci_open(struct nfcmrvl_private *priv) +{ + struct nfcmrvl_i2c_drv_data *drv_data = priv->drv_data; + + if (!drv_data) + return -ENODEV; + + return 0; +} + +static int nfcmrvl_i2c_nci_close(struct nfcmrvl_private *priv) +{ + return 0; +} + +static int nfcmrvl_i2c_nci_send(struct nfcmrvl_private *priv, + struct sk_buff *skb) +{ + struct nfcmrvl_i2c_drv_data *drv_data = priv->drv_data; + int ret; + + if (test_bit(NFCMRVL_PHY_ERROR, &priv->flags)) + return -EREMOTEIO; + + ret = i2c_master_send(drv_data->i2c, skb->data, skb->len); + + /* Retry if chip was in standby */ + if (ret == -EREMOTEIO) { + nfc_info(drv_data->dev, "chip may sleep, retry\n"); + usleep_range(6000, 10000); + ret = i2c_master_send(drv_data->i2c, skb->data, skb->len); + } + + if (ret >= 0) { + if (ret != skb->len) { + nfc_err(drv_data->dev, + "Invalid length sent: %u (expected %u)\n", + ret, skb->len); + ret = -EREMOTEIO; + } else + ret = 0; + kfree_skb(skb); + } + + return ret; +} + +static void nfcmrvl_i2c_nci_update_config(struct nfcmrvl_private *priv, + const void *param) +{ +} + +static struct nfcmrvl_if_ops i2c_ops = { + .nci_open = nfcmrvl_i2c_nci_open, + .nci_close = nfcmrvl_i2c_nci_close, + .nci_send = nfcmrvl_i2c_nci_send, + .nci_update_config = nfcmrvl_i2c_nci_update_config, +}; + +static int nfcmrvl_i2c_parse_dt(struct device_node *node, + struct nfcmrvl_platform_data *pdata) +{ + int ret; + + ret = nfcmrvl_parse_dt(node, pdata); + if (ret < 0) { + pr_err("Failed to get generic entries\n"); + return ret; + } + + if (of_find_property(node, "i2c-int-falling", NULL)) + pdata->irq_polarity = IRQF_TRIGGER_FALLING; + else + pdata->irq_polarity = IRQF_TRIGGER_RISING; + + ret = irq_of_parse_and_map(node, 0); + if (ret < 0) { + pr_err("Unable to get irq, error: %d\n", ret); + return ret; + } + pdata->irq = ret; + + return 0; +} + +static int nfcmrvl_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct nfcmrvl_i2c_drv_data *drv_data; + struct nfcmrvl_platform_data *pdata; + struct nfcmrvl_platform_data config; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + nfc_err(&client->dev, "Need I2C_FUNC_I2C\n"); + return -ENODEV; + } + + drv_data = devm_kzalloc(&client->dev, sizeof(*drv_data), GFP_KERNEL); + if (!drv_data) + return -ENOMEM; + + drv_data->i2c = client; + drv_data->dev = &client->dev; + drv_data->priv = NULL; + + i2c_set_clientdata(client, drv_data); + + pdata = client->dev.platform_data; + + if (!pdata && client->dev.of_node) + if (nfcmrvl_i2c_parse_dt(client->dev.of_node, &config) == 0) + pdata = &config; + + if (!pdata) + return -EINVAL; + + /* Request the read IRQ */ + ret = devm_request_threaded_irq(&drv_data->i2c->dev, pdata->irq, + NULL, nfcmrvl_i2c_int_irq_thread_fn, + pdata->irq_polarity | IRQF_ONESHOT, + "nfcmrvl_i2c_int", drv_data); + if (ret < 0) { + nfc_err(&drv_data->i2c->dev, + "Unable to register IRQ handler\n"); + return ret; + } + + drv_data->priv = nfcmrvl_nci_register_dev(NFCMRVL_PHY_I2C, + drv_data, &i2c_ops, + &drv_data->i2c->dev, pdata); + + if (IS_ERR(drv_data->priv)) + return PTR_ERR(drv_data->priv); + + drv_data->priv->support_fw_dnld = true; + + return 0; +} + +static int nfcmrvl_i2c_remove(struct i2c_client *client) +{ + struct nfcmrvl_i2c_drv_data *drv_data = i2c_get_clientdata(client); + + nfcmrvl_nci_unregister_dev(drv_data->priv); + + return 0; +} + + +static const struct of_device_id of_nfcmrvl_i2c_match[] = { + { .compatible = "mrvl,nfc-i2c", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_nfcmrvl_i2c_match); + +static struct i2c_device_id nfcmrvl_i2c_id_table[] = { + { "nfcmrvl_i2c", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, nfcmrvl_i2c_id_table); + +static struct i2c_driver nfcmrvl_i2c_driver = { + .probe = nfcmrvl_i2c_probe, + .id_table = nfcmrvl_i2c_id_table, + .remove = nfcmrvl_i2c_remove, + .driver = { + .name = "nfcmrvl_i2c", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(of_nfcmrvl_i2c_match), + }, +}; + +module_i2c_driver(nfcmrvl_i2c_driver); + +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_DESCRIPTION("Marvell NFC-over-I2C driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nfc/nfcmrvl/main.c b/drivers/nfc/nfcmrvl/main.c index a24a7ca9f33d..0c27de60a6bd 100644 --- a/drivers/nfc/nfcmrvl/main.c +++ b/drivers/nfc/nfcmrvl/main.c @@ -33,6 +33,9 @@ static int nfcmrvl_nci_open(struct nci_dev *ndev) if (test_and_set_bit(NFCMRVL_NCI_RUNNING, &priv->flags)) return 0; + /* Reset possible fault of previous session */ + clear_bit(NFCMRVL_PHY_ERROR, &priv->flags); + err = priv->if_ops->nci_open(priv); if (err) @@ -226,10 +229,8 @@ EXPORT_SYMBOL_GPL(nfcmrvl_nci_recv_frame); void nfcmrvl_chip_reset(struct nfcmrvl_private *priv) { - /* - * This function does not take care if someone is using the device. - * To be improved. - */ + /* Reset possible fault of previous session */ + clear_bit(NFCMRVL_PHY_ERROR, &priv->flags); if (priv->config.reset_n_io) { nfc_info(priv->dev, "reset the chip\n"); diff --git a/drivers/nfc/nfcmrvl/nfcmrvl.h b/drivers/nfc/nfcmrvl/nfcmrvl.h index f82678be5aa9..de68ff45e49a 100644 --- a/drivers/nfc/nfcmrvl/nfcmrvl.h +++ b/drivers/nfc/nfcmrvl/nfcmrvl.h @@ -25,6 +25,7 @@ /* Define private flags: */ #define NFCMRVL_NCI_RUNNING 1 +#define NFCMRVL_PHY_ERROR 2 #define NFCMRVL_EXT_COEX_ID 0xE0 #define NFCMRVL_NOT_ALLOWED_ID 0xE1 |