diff options
Diffstat (limited to '')
-rw-r--r-- | drivers/nfc/nfcmrvl/Kconfig | 54 | ||||
-rw-r--r-- | drivers/nfc/nfcmrvl/Makefile | 19 | ||||
-rw-r--r-- | drivers/nfc/nfcmrvl/fw_dnld.c | 559 | ||||
-rw-r--r-- | drivers/nfc/nfcmrvl/fw_dnld.h | 98 | ||||
-rw-r--r-- | drivers/nfc/nfcmrvl/i2c.c | 290 | ||||
-rw-r--r-- | drivers/nfc/nfcmrvl/main.c | 287 | ||||
-rw-r--r-- | drivers/nfc/nfcmrvl/nfcmrvl.h | 117 | ||||
-rw-r--r-- | drivers/nfc/nfcmrvl/spi.c | 226 | ||||
-rw-r--r-- | drivers/nfc/nfcmrvl/uart.c | 235 | ||||
-rw-r--r-- | drivers/nfc/nfcmrvl/usb.c | 484 |
10 files changed, 2369 insertions, 0 deletions
diff --git a/drivers/nfc/nfcmrvl/Kconfig b/drivers/nfc/nfcmrvl/Kconfig new file mode 100644 index 000000000..670af7692 --- /dev/null +++ b/drivers/nfc/nfcmrvl/Kconfig @@ -0,0 +1,54 @@ +config NFC_MRVL + tristate + help + The core driver to support Marvell NFC devices. + + This driver is required if you want to support + Marvell NFC device 8897. + +config NFC_MRVL_USB + tristate "Marvell NFC-over-USB driver" + depends on NFC_NCI && USB + select NFC_MRVL + help + Marvell NFC-over-USB driver. + + This driver provides support for Marvell NFC-over-USB devices: + 8897. + + Say Y here to compile support for Marvell NFC-over-USB driver + into the kernel or say M to compile it as module. + +config NFC_MRVL_UART + tristate "Marvell NFC-over-UART driver" + depends on NFC_NCI && NFC_NCI_UART + select NFC_MRVL + help + Marvell NFC-over-UART driver. + + This driver provides support for Marvell NFC-over-UART devices + + 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. + +config NFC_MRVL_SPI + tristate "Marvell NFC-over-SPI driver" + depends on NFC_MRVL && NFC_NCI_SPI + help + Marvell NFC-over-SPI driver. + + This driver provides support for Marvell NFC-over-SPI devices. + + Say Y here to compile support for Marvell NFC-over-SPI driver + into the kernel or say M to compile it as module. diff --git a/drivers/nfc/nfcmrvl/Makefile b/drivers/nfc/nfcmrvl/Makefile new file mode 100644 index 000000000..e74de0cb3 --- /dev/null +++ b/drivers/nfc/nfcmrvl/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for NFCMRVL NCI based NFC driver +# + +nfcmrvl-y += main.o fw_dnld.o +obj-$(CONFIG_NFC_MRVL) += nfcmrvl.o + +nfcmrvl_usb-y += usb.o +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 + +nfcmrvl_spi-y += spi.o +obj-$(CONFIG_NFC_MRVL_SPI) += nfcmrvl_spi.o diff --git a/drivers/nfc/nfcmrvl/fw_dnld.c b/drivers/nfc/nfcmrvl/fw_dnld.c new file mode 100644 index 000000000..52c8ae504 --- /dev/null +++ b/drivers/nfc/nfcmrvl/fw_dnld.c @@ -0,0 +1,559 @@ +/* + * Marvell NFC driver: Firmware downloader + * + * 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 <asm/unaligned.h> +#include <linux/firmware.h> +#include <linux/nfc.h> +#include <net/nfc/nci.h> +#include <net/nfc/nci_core.h> +#include "nfcmrvl.h" + +#define FW_DNLD_TIMEOUT 15000 + +#define NCI_OP_PROPRIETARY_BOOT_CMD nci_opcode_pack(NCI_GID_PROPRIETARY, \ + NCI_OP_PROP_BOOT_CMD) + +/* FW download states */ + +enum { + STATE_RESET = 0, + STATE_INIT, + STATE_SET_REF_CLOCK, + STATE_SET_HI_CONFIG, + STATE_OPEN_LC, + STATE_FW_DNLD, + STATE_CLOSE_LC, + STATE_BOOT +}; + +enum { + SUBSTATE_WAIT_COMMAND = 0, + SUBSTATE_WAIT_ACK_CREDIT, + SUBSTATE_WAIT_NACK_CREDIT, + SUBSTATE_WAIT_DATA_CREDIT, +}; + +/* +** Patterns for responses +*/ + +static const uint8_t nci_pattern_core_reset_ntf[] = { + 0x60, 0x00, 0x02, 0xA0, 0x01 +}; + +static const uint8_t nci_pattern_core_init_rsp[] = { + 0x40, 0x01, 0x11 +}; + +static const uint8_t nci_pattern_core_set_config_rsp[] = { + 0x40, 0x02, 0x02, 0x00, 0x00 +}; + +static const uint8_t nci_pattern_core_conn_create_rsp[] = { + 0x40, 0x04, 0x04, 0x00 +}; + +static const uint8_t nci_pattern_core_conn_close_rsp[] = { + 0x40, 0x05, 0x01, 0x00 +}; + +static const uint8_t nci_pattern_core_conn_credits_ntf[] = { + 0x60, 0x06, 0x03, 0x01, NCI_CORE_LC_CONNID_PROP_FW_DL, 0x01 +}; + +static const uint8_t nci_pattern_proprietary_boot_rsp[] = { + 0x4F, 0x3A, 0x01, 0x00 +}; + +static struct sk_buff *alloc_lc_skb(struct nfcmrvl_private *priv, uint8_t plen) +{ + struct sk_buff *skb; + struct nci_data_hdr *hdr; + + skb = nci_skb_alloc(priv->ndev, (NCI_DATA_HDR_SIZE + plen), GFP_KERNEL); + if (!skb) { + pr_err("no memory for data\n"); + return NULL; + } + + hdr = skb_put(skb, NCI_DATA_HDR_SIZE); + hdr->conn_id = NCI_CORE_LC_CONNID_PROP_FW_DL; + hdr->rfu = 0; + hdr->plen = plen; + + nci_mt_set((__u8 *)hdr, NCI_MT_DATA_PKT); + nci_pbf_set((__u8 *)hdr, NCI_PBF_LAST); + + return skb; +} + +static void fw_dnld_over(struct nfcmrvl_private *priv, u32 error) +{ + if (priv->fw_dnld.fw) { + release_firmware(priv->fw_dnld.fw); + priv->fw_dnld.fw = NULL; + priv->fw_dnld.header = NULL; + priv->fw_dnld.binary_config = NULL; + } + + atomic_set(&priv->ndev->cmd_cnt, 0); + + if (timer_pending(&priv->ndev->cmd_timer)) + del_timer_sync(&priv->ndev->cmd_timer); + + if (timer_pending(&priv->fw_dnld.timer)) + del_timer_sync(&priv->fw_dnld.timer); + + nfc_info(priv->dev, "FW loading over (%d)]\n", error); + + if (error != 0) { + /* failed, halt the chip to avoid power consumption */ + nfcmrvl_chip_halt(priv); + } + + nfc_fw_download_done(priv->ndev->nfc_dev, priv->fw_dnld.name, error); +} + +static void fw_dnld_timeout(struct timer_list *t) +{ + struct nfcmrvl_private *priv = from_timer(priv, t, fw_dnld.timer); + + nfc_err(priv->dev, "FW loading timeout"); + priv->fw_dnld.state = STATE_RESET; + fw_dnld_over(priv, -ETIMEDOUT); +} + +static int process_state_reset(struct nfcmrvl_private *priv, + struct sk_buff *skb) +{ + if (sizeof(nci_pattern_core_reset_ntf) != skb->len || + memcmp(skb->data, nci_pattern_core_reset_ntf, + sizeof(nci_pattern_core_reset_ntf))) + return -EINVAL; + + nfc_info(priv->dev, "BootROM reset, start fw download\n"); + + /* Start FW download state machine */ + priv->fw_dnld.state = STATE_INIT; + nci_send_cmd(priv->ndev, NCI_OP_CORE_INIT_CMD, 0, NULL); + + return 0; +} + +static int process_state_init(struct nfcmrvl_private *priv, struct sk_buff *skb) +{ + struct nci_core_set_config_cmd cmd; + + if (sizeof(nci_pattern_core_init_rsp) >= skb->len || + memcmp(skb->data, nci_pattern_core_init_rsp, + sizeof(nci_pattern_core_init_rsp))) + return -EINVAL; + + cmd.num_params = 1; + cmd.param.id = NFCMRVL_PROP_REF_CLOCK; + cmd.param.len = 4; + memcpy(cmd.param.val, &priv->fw_dnld.header->ref_clock, 4); + + nci_send_cmd(priv->ndev, NCI_OP_CORE_SET_CONFIG_CMD, 3 + cmd.param.len, + &cmd); + + priv->fw_dnld.state = STATE_SET_REF_CLOCK; + return 0; +} + +static void create_lc(struct nfcmrvl_private *priv) +{ + uint8_t param[2] = { NCI_CORE_LC_PROP_FW_DL, 0x0 }; + + priv->fw_dnld.state = STATE_OPEN_LC; + nci_send_cmd(priv->ndev, NCI_OP_CORE_CONN_CREATE_CMD, 2, param); +} + +static int process_state_set_ref_clock(struct nfcmrvl_private *priv, + struct sk_buff *skb) +{ + struct nci_core_set_config_cmd cmd; + + if (sizeof(nci_pattern_core_set_config_rsp) != skb->len || + memcmp(skb->data, nci_pattern_core_set_config_rsp, skb->len)) + return -EINVAL; + + cmd.num_params = 1; + cmd.param.id = NFCMRVL_PROP_SET_HI_CONFIG; + + switch (priv->phy) { + case NFCMRVL_PHY_UART: + cmd.param.len = 5; + memcpy(cmd.param.val, + &priv->fw_dnld.binary_config->uart.baudrate, + 4); + cmd.param.val[4] = + priv->fw_dnld.binary_config->uart.flow_control; + break; + case NFCMRVL_PHY_I2C: + cmd.param.len = 5; + memcpy(cmd.param.val, + &priv->fw_dnld.binary_config->i2c.clk, + 4); + cmd.param.val[4] = 0; + break; + case NFCMRVL_PHY_SPI: + cmd.param.len = 5; + memcpy(cmd.param.val, + &priv->fw_dnld.binary_config->spi.clk, + 4); + cmd.param.val[4] = 0; + break; + default: + create_lc(priv); + return 0; + } + + priv->fw_dnld.state = STATE_SET_HI_CONFIG; + nci_send_cmd(priv->ndev, NCI_OP_CORE_SET_CONFIG_CMD, 3 + cmd.param.len, + &cmd); + return 0; +} + +static int process_state_set_hi_config(struct nfcmrvl_private *priv, + struct sk_buff *skb) +{ + if (sizeof(nci_pattern_core_set_config_rsp) != skb->len || + memcmp(skb->data, nci_pattern_core_set_config_rsp, skb->len)) + return -EINVAL; + + create_lc(priv); + return 0; +} + +static int process_state_open_lc(struct nfcmrvl_private *priv, + struct sk_buff *skb) +{ + if (sizeof(nci_pattern_core_conn_create_rsp) >= skb->len || + memcmp(skb->data, nci_pattern_core_conn_create_rsp, + sizeof(nci_pattern_core_conn_create_rsp))) + return -EINVAL; + + priv->fw_dnld.state = STATE_FW_DNLD; + priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND; + priv->fw_dnld.offset = priv->fw_dnld.binary_config->offset; + return 0; +} + +static int process_state_fw_dnld(struct nfcmrvl_private *priv, + struct sk_buff *skb) +{ + uint16_t len; + uint16_t comp_len; + struct sk_buff *out_skb; + + switch (priv->fw_dnld.substate) { + case SUBSTATE_WAIT_COMMAND: + /* + * Command format: + * B0..2: NCI header + * B3 : Helper command (0xA5) + * B4..5: le16 data size + * B6..7: le16 data size complement (~) + * B8..N: payload + */ + + /* Remove NCI HDR */ + skb_pull(skb, 3); + if (skb->data[0] != HELPER_CMD_PACKET_FORMAT || skb->len != 5) { + nfc_err(priv->dev, "bad command"); + return -EINVAL; + } + skb_pull(skb, 1); + len = get_unaligned_le16(skb->data); + skb_pull(skb, 2); + comp_len = get_unaligned_le16(skb->data); + memcpy(&comp_len, skb->data, 2); + skb_pull(skb, 2); + if (((~len) & 0xFFFF) != comp_len) { + nfc_err(priv->dev, "bad len complement: %x %x %x", + len, comp_len, (~len & 0xFFFF)); + out_skb = alloc_lc_skb(priv, 1); + if (!out_skb) + return -ENOMEM; + skb_put_u8(out_skb, 0xBF); + nci_send_frame(priv->ndev, out_skb); + priv->fw_dnld.substate = SUBSTATE_WAIT_NACK_CREDIT; + return 0; + } + priv->fw_dnld.chunk_len = len; + out_skb = alloc_lc_skb(priv, 1); + if (!out_skb) + return -ENOMEM; + skb_put_u8(out_skb, HELPER_ACK_PACKET_FORMAT); + nci_send_frame(priv->ndev, out_skb); + priv->fw_dnld.substate = SUBSTATE_WAIT_ACK_CREDIT; + break; + + case SUBSTATE_WAIT_ACK_CREDIT: + if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || + memcmp(nci_pattern_core_conn_credits_ntf, skb->data, + skb->len)) { + nfc_err(priv->dev, "bad packet: waiting for credit"); + return -EINVAL; + } + if (priv->fw_dnld.chunk_len == 0) { + /* FW Loading is done */ + uint8_t conn_id = NCI_CORE_LC_CONNID_PROP_FW_DL; + + priv->fw_dnld.state = STATE_CLOSE_LC; + nci_send_cmd(priv->ndev, NCI_OP_CORE_CONN_CLOSE_CMD, + 1, &conn_id); + } else { + out_skb = alloc_lc_skb(priv, priv->fw_dnld.chunk_len); + if (!out_skb) + return -ENOMEM; + skb_put_data(out_skb, + ((uint8_t *)priv->fw_dnld.fw->data) + priv->fw_dnld.offset, + priv->fw_dnld.chunk_len); + nci_send_frame(priv->ndev, out_skb); + priv->fw_dnld.substate = SUBSTATE_WAIT_DATA_CREDIT; + } + break; + + case SUBSTATE_WAIT_DATA_CREDIT: + if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || + memcmp(nci_pattern_core_conn_credits_ntf, skb->data, + skb->len)) { + nfc_err(priv->dev, "bad packet: waiting for credit"); + return -EINVAL; + } + priv->fw_dnld.offset += priv->fw_dnld.chunk_len; + priv->fw_dnld.chunk_len = 0; + priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND; + break; + + case SUBSTATE_WAIT_NACK_CREDIT: + if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || + memcmp(nci_pattern_core_conn_credits_ntf, skb->data, + skb->len)) { + nfc_err(priv->dev, "bad packet: waiting for credit"); + return -EINVAL; + } + priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND; + break; + } + return 0; +} + +static int process_state_close_lc(struct nfcmrvl_private *priv, + struct sk_buff *skb) +{ + if (sizeof(nci_pattern_core_conn_close_rsp) != skb->len || + memcmp(skb->data, nci_pattern_core_conn_close_rsp, skb->len)) + return -EINVAL; + + priv->fw_dnld.state = STATE_BOOT; + nci_send_cmd(priv->ndev, NCI_OP_PROPRIETARY_BOOT_CMD, 0, NULL); + return 0; +} + +static int process_state_boot(struct nfcmrvl_private *priv, struct sk_buff *skb) +{ + if (sizeof(nci_pattern_proprietary_boot_rsp) != skb->len || + memcmp(skb->data, nci_pattern_proprietary_boot_rsp, skb->len)) + return -EINVAL; + + /* + * Update HI config to use the right configuration for the next + * data exchanges. + */ + priv->if_ops->nci_update_config(priv, + &priv->fw_dnld.binary_config->config); + + if (priv->fw_dnld.binary_config == &priv->fw_dnld.header->helper) { + /* + * This is the case where an helper was needed and we have + * uploaded it. Now we have to wait the next RESET NTF to start + * FW download. + */ + priv->fw_dnld.state = STATE_RESET; + priv->fw_dnld.binary_config = &priv->fw_dnld.header->firmware; + nfc_info(priv->dev, "FW loading: helper loaded"); + } else { + nfc_info(priv->dev, "FW loading: firmware loaded"); + fw_dnld_over(priv, 0); + } + return 0; +} + +static void fw_dnld_rx_work(struct work_struct *work) +{ + int ret; + struct sk_buff *skb; + struct nfcmrvl_fw_dnld *fw_dnld = container_of(work, + struct nfcmrvl_fw_dnld, + rx_work); + struct nfcmrvl_private *priv = container_of(fw_dnld, + struct nfcmrvl_private, + fw_dnld); + + while ((skb = skb_dequeue(&fw_dnld->rx_q))) { + nfc_send_to_raw_sock(priv->ndev->nfc_dev, skb, + RAW_PAYLOAD_NCI, NFC_DIRECTION_RX); + switch (fw_dnld->state) { + case STATE_RESET: + ret = process_state_reset(priv, skb); + break; + case STATE_INIT: + ret = process_state_init(priv, skb); + break; + case STATE_SET_REF_CLOCK: + ret = process_state_set_ref_clock(priv, skb); + break; + case STATE_SET_HI_CONFIG: + ret = process_state_set_hi_config(priv, skb); + break; + case STATE_OPEN_LC: + ret = process_state_open_lc(priv, skb); + break; + case STATE_FW_DNLD: + ret = process_state_fw_dnld(priv, skb); + break; + case STATE_CLOSE_LC: + ret = process_state_close_lc(priv, skb); + break; + case STATE_BOOT: + ret = process_state_boot(priv, skb); + break; + default: + ret = -EFAULT; + } + + kfree_skb(skb); + + if (ret != 0) { + nfc_err(priv->dev, "FW loading error"); + fw_dnld_over(priv, ret); + break; + } + } +} + +int nfcmrvl_fw_dnld_init(struct nfcmrvl_private *priv) +{ + char name[32]; + + INIT_WORK(&priv->fw_dnld.rx_work, fw_dnld_rx_work); + snprintf(name, sizeof(name), "%s_nfcmrvl_fw_dnld_rx_wq", + dev_name(&priv->ndev->nfc_dev->dev)); + priv->fw_dnld.rx_wq = create_singlethread_workqueue(name); + if (!priv->fw_dnld.rx_wq) + return -ENOMEM; + skb_queue_head_init(&priv->fw_dnld.rx_q); + return 0; +} + +void nfcmrvl_fw_dnld_deinit(struct nfcmrvl_private *priv) +{ + destroy_workqueue(priv->fw_dnld.rx_wq); +} + +void nfcmrvl_fw_dnld_recv_frame(struct nfcmrvl_private *priv, + struct sk_buff *skb) +{ + /* Discard command timer */ + if (timer_pending(&priv->ndev->cmd_timer)) + del_timer_sync(&priv->ndev->cmd_timer); + + /* Allow next command */ + atomic_set(&priv->ndev->cmd_cnt, 1); + + /* Queue and trigger rx work */ + skb_queue_tail(&priv->fw_dnld.rx_q, skb); + queue_work(priv->fw_dnld.rx_wq, &priv->fw_dnld.rx_work); +} + +void nfcmrvl_fw_dnld_abort(struct nfcmrvl_private *priv) +{ + fw_dnld_over(priv, -EHOSTDOWN); +} + +int nfcmrvl_fw_dnld_start(struct nci_dev *ndev, const char *firmware_name) +{ + struct nfcmrvl_private *priv = nci_get_drvdata(ndev); + struct nfcmrvl_fw_dnld *fw_dnld = &priv->fw_dnld; + int res; + + if (!priv->support_fw_dnld) + return -ENOTSUPP; + + if (!firmware_name || !firmware_name[0]) + return -EINVAL; + + strcpy(fw_dnld->name, firmware_name); + + /* + * Retrieve FW binary file and parse it to initialize FW download + * state machine. + */ + + /* Retrieve FW binary */ + res = request_firmware(&fw_dnld->fw, firmware_name, + &ndev->nfc_dev->dev); + if (res < 0) { + nfc_err(priv->dev, "failed to retrieve FW %s", firmware_name); + return -ENOENT; + } + + fw_dnld->header = (const struct nfcmrvl_fw *) priv->fw_dnld.fw->data; + + if (fw_dnld->header->magic != NFCMRVL_FW_MAGIC || + fw_dnld->header->phy != priv->phy) { + nfc_err(priv->dev, "bad firmware binary %s magic=0x%x phy=%d", + firmware_name, fw_dnld->header->magic, + fw_dnld->header->phy); + release_firmware(fw_dnld->fw); + fw_dnld->header = NULL; + return -EINVAL; + } + + if (fw_dnld->header->helper.offset != 0) { + nfc_info(priv->dev, "loading helper"); + fw_dnld->binary_config = &fw_dnld->header->helper; + } else { + nfc_info(priv->dev, "loading firmware"); + fw_dnld->binary_config = &fw_dnld->header->firmware; + } + + /* Configure a timer for timeout */ + timer_setup(&priv->fw_dnld.timer, fw_dnld_timeout, 0); + mod_timer(&priv->fw_dnld.timer, + jiffies + msecs_to_jiffies(FW_DNLD_TIMEOUT)); + + /* Ronfigure HI to be sure that it is the bootrom values */ + priv->if_ops->nci_update_config(priv, + &fw_dnld->header->bootrom.config); + + /* Allow first command */ + atomic_set(&priv->ndev->cmd_cnt, 1); + + /* First, reset the chip */ + priv->fw_dnld.state = STATE_RESET; + nfcmrvl_chip_reset(priv); + + /* Now wait for CORE_RESET_NTF or timeout */ + + return 0; +} diff --git a/drivers/nfc/nfcmrvl/fw_dnld.h b/drivers/nfc/nfcmrvl/fw_dnld.h new file mode 100644 index 000000000..ee4a339c0 --- /dev/null +++ b/drivers/nfc/nfcmrvl/fw_dnld.h @@ -0,0 +1,98 @@ +/** + * Marvell NFC driver: Firmware downloader + * + * 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. + **/ + +#ifndef __NFCMRVL_FW_DNLD_H__ +#define __NFCMRVL_FW_DNLD_H__ + +#include <linux/workqueue.h> + +#define NFCMRVL_FW_MAGIC 0x88888888 + +#define NCI_OP_PROP_BOOT_CMD 0x3A + +#define NCI_CORE_LC_PROP_FW_DL 0xFD +#define NCI_CORE_LC_CONNID_PROP_FW_DL 0x02 + +#define HELPER_CMD_ENTRY_POINT 0x04 +#define HELPER_CMD_PACKET_FORMAT 0xA5 +#define HELPER_ACK_PACKET_FORMAT 0x5A +#define HELPER_RETRY_REQUESTED (1 << 15) + +struct nfcmrvl_private; + +struct nfcmrvl_fw_uart_config { + uint8_t flow_control; + uint32_t baudrate; +} __packed; + +struct nfcmrvl_fw_i2c_config { + uint32_t clk; +} __packed; + +struct nfcmrvl_fw_spi_config { + uint32_t clk; +} __packed; + +struct nfcmrvl_fw_binary_config { + uint32_t offset; + union { + void *config; + struct nfcmrvl_fw_uart_config uart; + struct nfcmrvl_fw_i2c_config i2c; + struct nfcmrvl_fw_spi_config spi; + uint8_t reserved[64]; + }; +} __packed; + +struct nfcmrvl_fw { + uint32_t magic; + uint32_t ref_clock; + uint32_t phy; + struct nfcmrvl_fw_binary_config bootrom; + struct nfcmrvl_fw_binary_config helper; + struct nfcmrvl_fw_binary_config firmware; + uint8_t reserved[64]; +} __packed; + +struct nfcmrvl_fw_dnld { + char name[NFC_FIRMWARE_NAME_MAXSIZE + 1]; + const struct firmware *fw; + + const struct nfcmrvl_fw *header; + const struct nfcmrvl_fw_binary_config *binary_config; + + int state; + int substate; + int offset; + int chunk_len; + + struct workqueue_struct *rx_wq; + struct work_struct rx_work; + struct sk_buff_head rx_q; + + struct timer_list timer; +}; + +int nfcmrvl_fw_dnld_init(struct nfcmrvl_private *priv); +void nfcmrvl_fw_dnld_deinit(struct nfcmrvl_private *priv); +void nfcmrvl_fw_dnld_abort(struct nfcmrvl_private *priv); +int nfcmrvl_fw_dnld_start(struct nci_dev *ndev, const char *firmware_name); +void nfcmrvl_fw_dnld_recv_frame(struct nfcmrvl_private *priv, + struct sk_buff *skb); + +#endif diff --git a/drivers/nfc/nfcmrvl/i2c.c b/drivers/nfc/nfcmrvl/i2c.c new file mode 100644 index 000000000..0f2237988 --- /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 */ + skb_put_data(*skb, &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 = "marvell,nfc-i2c", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_nfcmrvl_i2c_match); + +static const 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 new file mode 100644 index 000000000..54d228acc --- /dev/null +++ b/drivers/nfc/nfcmrvl/main.c @@ -0,0 +1,287 @@ +/* + * Marvell NFC driver: major functions + * + * Copyright (C) 2014-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/gpio.h> +#include <linux/delay.h> +#include <linux/of_gpio.h> +#include <linux/nfc.h> +#include <net/nfc/nci.h> +#include <net/nfc/nci_core.h> +#include "nfcmrvl.h" + +static int nfcmrvl_nci_open(struct nci_dev *ndev) +{ + struct nfcmrvl_private *priv = nci_get_drvdata(ndev); + int err; + + 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) + clear_bit(NFCMRVL_NCI_RUNNING, &priv->flags); + + return err; +} + +static int nfcmrvl_nci_close(struct nci_dev *ndev) +{ + struct nfcmrvl_private *priv = nci_get_drvdata(ndev); + + if (!test_and_clear_bit(NFCMRVL_NCI_RUNNING, &priv->flags)) + return 0; + + priv->if_ops->nci_close(priv); + + return 0; +} + +static int nfcmrvl_nci_send(struct nci_dev *ndev, struct sk_buff *skb) +{ + struct nfcmrvl_private *priv = nci_get_drvdata(ndev); + + nfc_info(priv->dev, "send entry, len %d\n", skb->len); + + skb->dev = (void *)ndev; + + if (priv->config.hci_muxed) { + unsigned char *hdr; + unsigned char len = skb->len; + + hdr = skb_push(skb, NFCMRVL_HCI_EVENT_HEADER_SIZE); + hdr[0] = NFCMRVL_HCI_COMMAND_CODE; + hdr[1] = NFCMRVL_HCI_OGF; + hdr[2] = NFCMRVL_HCI_OCF; + hdr[3] = len; + } + + return priv->if_ops->nci_send(priv, skb); +} + +static int nfcmrvl_nci_setup(struct nci_dev *ndev) +{ + __u8 val = 1; + + nci_set_config(ndev, NFCMRVL_PB_BAIL_OUT, 1, &val); + return 0; +} + +static int nfcmrvl_nci_fw_download(struct nci_dev *ndev, + const char *firmware_name) +{ + return nfcmrvl_fw_dnld_start(ndev, firmware_name); +} + +static struct nci_ops nfcmrvl_nci_ops = { + .open = nfcmrvl_nci_open, + .close = nfcmrvl_nci_close, + .send = nfcmrvl_nci_send, + .setup = nfcmrvl_nci_setup, + .fw_download = nfcmrvl_nci_fw_download, +}; + +struct nfcmrvl_private *nfcmrvl_nci_register_dev(enum nfcmrvl_phy phy, + void *drv_data, + struct nfcmrvl_if_ops *ops, + struct device *dev, + struct nfcmrvl_platform_data *pdata) +{ + struct nfcmrvl_private *priv; + int rc; + int headroom; + int tailroom; + u32 protocols; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return ERR_PTR(-ENOMEM); + + priv->drv_data = drv_data; + priv->if_ops = ops; + priv->dev = dev; + priv->phy = phy; + + memcpy(&priv->config, pdata, sizeof(*pdata)); + + if (gpio_is_valid(priv->config.reset_n_io)) { + rc = gpio_request_one(priv->config.reset_n_io, + GPIOF_OUT_INIT_LOW, + "nfcmrvl_reset_n"); + if (rc < 0) { + priv->config.reset_n_io = -EINVAL; + nfc_err(dev, "failed to request reset_n io\n"); + } + } + + if (phy == NFCMRVL_PHY_SPI) { + headroom = NCI_SPI_HDR_LEN; + tailroom = 1; + } else + headroom = tailroom = 0; + + if (priv->config.hci_muxed) + headroom += NFCMRVL_HCI_EVENT_HEADER_SIZE; + + protocols = NFC_PROTO_JEWEL_MASK + | NFC_PROTO_MIFARE_MASK + | NFC_PROTO_FELICA_MASK + | NFC_PROTO_ISO14443_MASK + | NFC_PROTO_ISO14443_B_MASK + | NFC_PROTO_ISO15693_MASK + | NFC_PROTO_NFC_DEP_MASK; + + priv->ndev = nci_allocate_device(&nfcmrvl_nci_ops, protocols, + headroom, tailroom); + if (!priv->ndev) { + nfc_err(dev, "nci_allocate_device failed\n"); + rc = -ENOMEM; + goto error_free_gpio; + } + + rc = nfcmrvl_fw_dnld_init(priv); + if (rc) { + nfc_err(dev, "failed to initialize FW download %d\n", rc); + goto error_free_dev; + } + + nci_set_drvdata(priv->ndev, priv); + + rc = nci_register_device(priv->ndev); + if (rc) { + nfc_err(dev, "nci_register_device failed %d\n", rc); + goto error_fw_dnld_deinit; + } + + /* Ensure that controller is powered off */ + nfcmrvl_chip_halt(priv); + + nfc_info(dev, "registered with nci successfully\n"); + return priv; + +error_fw_dnld_deinit: + nfcmrvl_fw_dnld_deinit(priv); +error_free_dev: + nci_free_device(priv->ndev); +error_free_gpio: + if (gpio_is_valid(priv->config.reset_n_io)) + gpio_free(priv->config.reset_n_io); + kfree(priv); + return ERR_PTR(rc); +} +EXPORT_SYMBOL_GPL(nfcmrvl_nci_register_dev); + +void nfcmrvl_nci_unregister_dev(struct nfcmrvl_private *priv) +{ + struct nci_dev *ndev = priv->ndev; + + nci_unregister_device(ndev); + if (priv->ndev->nfc_dev->fw_download_in_progress) + nfcmrvl_fw_dnld_abort(priv); + + nfcmrvl_fw_dnld_deinit(priv); + + if (gpio_is_valid(priv->config.reset_n_io)) + gpio_free(priv->config.reset_n_io); + + nci_free_device(ndev); + kfree(priv); +} +EXPORT_SYMBOL_GPL(nfcmrvl_nci_unregister_dev); + +int nfcmrvl_nci_recv_frame(struct nfcmrvl_private *priv, struct sk_buff *skb) +{ + if (priv->config.hci_muxed) { + if (skb->data[0] == NFCMRVL_HCI_EVENT_CODE && + skb->data[1] == NFCMRVL_HCI_NFC_EVENT_CODE) { + /* Data packet, let's extract NCI payload */ + skb_pull(skb, NFCMRVL_HCI_EVENT_HEADER_SIZE); + } else { + /* Skip this packet */ + kfree_skb(skb); + return 0; + } + } + + if (priv->ndev->nfc_dev->fw_download_in_progress) { + nfcmrvl_fw_dnld_recv_frame(priv, skb); + return 0; + } + + if (test_bit(NFCMRVL_NCI_RUNNING, &priv->flags)) + nci_recv_frame(priv->ndev, skb); + else { + /* Drop this packet since nobody wants it */ + kfree_skb(skb); + return 0; + } + + return 0; +} +EXPORT_SYMBOL_GPL(nfcmrvl_nci_recv_frame); + +void nfcmrvl_chip_reset(struct nfcmrvl_private *priv) +{ + /* Reset possible fault of previous session */ + clear_bit(NFCMRVL_PHY_ERROR, &priv->flags); + + if (gpio_is_valid(priv->config.reset_n_io)) { + nfc_info(priv->dev, "reset the chip\n"); + gpio_set_value(priv->config.reset_n_io, 0); + usleep_range(5000, 10000); + gpio_set_value(priv->config.reset_n_io, 1); + } else + nfc_info(priv->dev, "no reset available on this interface\n"); +} + +void nfcmrvl_chip_halt(struct nfcmrvl_private *priv) +{ + if (gpio_is_valid(priv->config.reset_n_io)) + gpio_set_value(priv->config.reset_n_io, 0); +} + +int nfcmrvl_parse_dt(struct device_node *node, + struct nfcmrvl_platform_data *pdata) +{ + int reset_n_io; + + reset_n_io = of_get_named_gpio(node, "reset-n-io", 0); + if (reset_n_io < 0) { + pr_info("no reset-n-io config\n"); + } else if (!gpio_is_valid(reset_n_io)) { + pr_err("invalid reset-n-io GPIO\n"); + return reset_n_io; + } + pdata->reset_n_io = reset_n_io; + + if (of_find_property(node, "hci-muxed", NULL)) + pdata->hci_muxed = 1; + else + pdata->hci_muxed = 0; + + return 0; +} +EXPORT_SYMBOL_GPL(nfcmrvl_parse_dt); + +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_DESCRIPTION("Marvell NFC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nfc/nfcmrvl/nfcmrvl.h b/drivers/nfc/nfcmrvl/nfcmrvl.h new file mode 100644 index 000000000..de68ff45e --- /dev/null +++ b/drivers/nfc/nfcmrvl/nfcmrvl.h @@ -0,0 +1,117 @@ +/** + * Marvell NFC driver + * + * Copyright (C) 2014-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. + **/ + +#ifndef _NFCMRVL_H_ +#define _NFCMRVL_H_ + +#include <linux/platform_data/nfcmrvl.h> + +#include "fw_dnld.h" + +/* 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 +#define NFCMRVL_ACTIVE_ID 0xE2 +#define NFCMRVL_EXT_COEX_ENABLE 1 +#define NFCMRVL_GPIO_PIN_NFC_NOT_ALLOWED 0xA +#define NFCMRVL_GPIO_PIN_NFC_ACTIVE 0xB +#define NFCMRVL_NCI_MAX_EVENT_SIZE 260 + +/* +** NCI FW Parmaters +*/ + +#define NFCMRVL_PB_BAIL_OUT 0x11 +#define NFCMRVL_PROP_REF_CLOCK 0xF0 +#define NFCMRVL_PROP_SET_HI_CONFIG 0xF1 + +/* +** HCI defines +*/ + +#define NFCMRVL_HCI_EVENT_HEADER_SIZE 0x04 +#define NFCMRVL_HCI_EVENT_CODE 0x04 +#define NFCMRVL_HCI_NFC_EVENT_CODE 0xFF +#define NFCMRVL_HCI_COMMAND_CODE 0x01 +#define NFCMRVL_HCI_OGF 0x81 +#define NFCMRVL_HCI_OCF 0xFE + +enum nfcmrvl_phy { + NFCMRVL_PHY_USB = 0, + NFCMRVL_PHY_UART = 1, + NFCMRVL_PHY_I2C = 2, + NFCMRVL_PHY_SPI = 3, +}; + +struct nfcmrvl_private { + + unsigned long flags; + + /* Platform configuration */ + struct nfcmrvl_platform_data config; + + /* Parent dev */ + struct nci_dev *ndev; + + /* FW download context */ + struct nfcmrvl_fw_dnld fw_dnld; + + /* FW download support */ + bool support_fw_dnld; + + /* + ** PHY related information + */ + + /* PHY driver context */ + void *drv_data; + /* PHY device */ + struct device *dev; + /* PHY type */ + enum nfcmrvl_phy phy; + /* Low level driver ops */ + struct nfcmrvl_if_ops *if_ops; +}; + +struct nfcmrvl_if_ops { + int (*nci_open) (struct nfcmrvl_private *priv); + int (*nci_close) (struct nfcmrvl_private *priv); + int (*nci_send) (struct nfcmrvl_private *priv, struct sk_buff *skb); + void (*nci_update_config)(struct nfcmrvl_private *priv, + const void *param); +}; + +void nfcmrvl_nci_unregister_dev(struct nfcmrvl_private *priv); +int nfcmrvl_nci_recv_frame(struct nfcmrvl_private *priv, struct sk_buff *skb); +struct nfcmrvl_private *nfcmrvl_nci_register_dev(enum nfcmrvl_phy phy, + void *drv_data, + struct nfcmrvl_if_ops *ops, + struct device *dev, + struct nfcmrvl_platform_data *pdata); + + +void nfcmrvl_chip_reset(struct nfcmrvl_private *priv); +void nfcmrvl_chip_halt(struct nfcmrvl_private *priv); + +int nfcmrvl_parse_dt(struct device_node *node, + struct nfcmrvl_platform_data *pdata); + +#endif diff --git a/drivers/nfc/nfcmrvl/spi.c b/drivers/nfc/nfcmrvl/spi.c new file mode 100644 index 000000000..8e0ddb434 --- /dev/null +++ b/drivers/nfc/nfcmrvl/spi.c @@ -0,0 +1,226 @@ +/** + * Marvell NFC-over-SPI driver: SPI 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/pm_runtime.h> +#include <linux/nfc.h> +#include <linux/gpio.h> +#include <linux/of_irq.h> +#include <linux/of_gpio.h> +#include <net/nfc/nci.h> +#include <net/nfc/nci_core.h> +#include <linux/spi/spi.h> +#include "nfcmrvl.h" + +#define SPI_WAIT_HANDSHAKE 1 + +struct nfcmrvl_spi_drv_data { + unsigned long flags; + struct spi_device *spi; + struct nci_spi *nci_spi; + struct completion handshake_completion; + struct nfcmrvl_private *priv; +}; + +static irqreturn_t nfcmrvl_spi_int_irq_thread_fn(int irq, void *drv_data_ptr) +{ + struct nfcmrvl_spi_drv_data *drv_data = drv_data_ptr; + struct sk_buff *skb; + + /* + * Special case where we are waiting for SPI_INT deassertion to start a + * transfer. + */ + if (test_and_clear_bit(SPI_WAIT_HANDSHAKE, &drv_data->flags)) { + complete(&drv_data->handshake_completion); + return IRQ_HANDLED; + } + + /* Normal case, SPI_INT deasserted by slave to trigger a master read */ + + skb = nci_spi_read(drv_data->nci_spi); + if (!skb) { + nfc_err(&drv_data->spi->dev, "failed to read spi packet"); + return IRQ_HANDLED; + } + + if (nfcmrvl_nci_recv_frame(drv_data->priv, skb) < 0) + nfc_err(&drv_data->spi->dev, "corrupted RX packet"); + + return IRQ_HANDLED; +} + +static int nfcmrvl_spi_nci_open(struct nfcmrvl_private *priv) +{ + return 0; +} + +static int nfcmrvl_spi_nci_close(struct nfcmrvl_private *priv) +{ + return 0; +} + +static int nfcmrvl_spi_nci_send(struct nfcmrvl_private *priv, + struct sk_buff *skb) +{ + struct nfcmrvl_spi_drv_data *drv_data = priv->drv_data; + int err; + + /* Reinit completion for slave handshake */ + reinit_completion(&drv_data->handshake_completion); + set_bit(SPI_WAIT_HANDSHAKE, &drv_data->flags); + + /* + * Append a dummy byte at the end of SPI frame. This is due to a + * specific DMA implementation in the controller + */ + skb_put(skb, 1); + + /* Send the SPI packet */ + err = nci_spi_send(drv_data->nci_spi, &drv_data->handshake_completion, + skb); + if (err) + nfc_err(priv->dev, "spi_send failed %d", err); + + return err; +} + +static void nfcmrvl_spi_nci_update_config(struct nfcmrvl_private *priv, + const void *param) +{ + struct nfcmrvl_spi_drv_data *drv_data = priv->drv_data; + const struct nfcmrvl_fw_spi_config *config = param; + + drv_data->nci_spi->xfer_speed_hz = config->clk; +} + +static struct nfcmrvl_if_ops spi_ops = { + .nci_open = nfcmrvl_spi_nci_open, + .nci_close = nfcmrvl_spi_nci_close, + .nci_send = nfcmrvl_spi_nci_send, + .nci_update_config = nfcmrvl_spi_nci_update_config, +}; + +static int nfcmrvl_spi_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; + } + + 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_spi_probe(struct spi_device *spi) +{ + struct nfcmrvl_platform_data *pdata; + struct nfcmrvl_platform_data config; + struct nfcmrvl_spi_drv_data *drv_data; + int ret = 0; + + drv_data = devm_kzalloc(&spi->dev, sizeof(*drv_data), GFP_KERNEL); + if (!drv_data) + return -ENOMEM; + + drv_data->spi = spi; + drv_data->priv = NULL; + spi_set_drvdata(spi, drv_data); + + pdata = spi->dev.platform_data; + + if (!pdata && spi->dev.of_node) + if (nfcmrvl_spi_parse_dt(spi->dev.of_node, &config) == 0) + pdata = &config; + + if (!pdata) + return -EINVAL; + + ret = devm_request_threaded_irq(&drv_data->spi->dev, pdata->irq, + NULL, nfcmrvl_spi_int_irq_thread_fn, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "nfcmrvl_spi_int", drv_data); + if (ret < 0) { + nfc_err(&drv_data->spi->dev, "Unable to register IRQ handler"); + return -ENODEV; + } + + drv_data->priv = nfcmrvl_nci_register_dev(NFCMRVL_PHY_SPI, + drv_data, &spi_ops, + &drv_data->spi->dev, + pdata); + if (IS_ERR(drv_data->priv)) + return PTR_ERR(drv_data->priv); + + drv_data->priv->support_fw_dnld = true; + + drv_data->nci_spi = nci_spi_allocate_spi(drv_data->spi, 0, 10, + drv_data->priv->ndev); + + /* Init completion for slave handshake */ + init_completion(&drv_data->handshake_completion); + return 0; +} + +static int nfcmrvl_spi_remove(struct spi_device *spi) +{ + struct nfcmrvl_spi_drv_data *drv_data = spi_get_drvdata(spi); + + nfcmrvl_nci_unregister_dev(drv_data->priv); + return 0; +} + +static const struct of_device_id of_nfcmrvl_spi_match[] = { + { .compatible = "marvell,nfc-spi", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_nfcmrvl_spi_match); + +static const struct spi_device_id nfcmrvl_spi_id_table[] = { + { "nfcmrvl_spi", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, nfcmrvl_spi_id_table); + +static struct spi_driver nfcmrvl_spi_driver = { + .probe = nfcmrvl_spi_probe, + .remove = nfcmrvl_spi_remove, + .id_table = nfcmrvl_spi_id_table, + .driver = { + .name = "nfcmrvl_spi", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(of_nfcmrvl_spi_match), + }, +}; + +module_spi_driver(nfcmrvl_spi_driver); + +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_DESCRIPTION("Marvell NFC-over-SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nfc/nfcmrvl/uart.c b/drivers/nfc/nfcmrvl/uart.c new file mode 100644 index 000000000..e5a622ce4 --- /dev/null +++ b/drivers/nfc/nfcmrvl/uart.c @@ -0,0 +1,235 @@ +/** + * Marvell NFC-over-UART driver + * + * 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/delay.h> +#include <linux/of_gpio.h> +#include <net/nfc/nci.h> +#include <net/nfc/nci_core.h> +#include "nfcmrvl.h" + +static unsigned int hci_muxed; +static unsigned int flow_control; +static unsigned int break_control; +static int reset_n_io = -EINVAL; + +/* +** NFCMRVL NCI OPS +*/ + +static int nfcmrvl_uart_nci_open(struct nfcmrvl_private *priv) +{ + return 0; +} + +static int nfcmrvl_uart_nci_close(struct nfcmrvl_private *priv) +{ + return 0; +} + +static int nfcmrvl_uart_nci_send(struct nfcmrvl_private *priv, + struct sk_buff *skb) +{ + struct nci_uart *nu = priv->drv_data; + + return nu->ops.send(nu, skb); +} + +static void nfcmrvl_uart_nci_update_config(struct nfcmrvl_private *priv, + const void *param) +{ + struct nci_uart *nu = priv->drv_data; + const struct nfcmrvl_fw_uart_config *config = param; + + nci_uart_set_config(nu, le32_to_cpu(config->baudrate), + config->flow_control); +} + +static struct nfcmrvl_if_ops uart_ops = { + .nci_open = nfcmrvl_uart_nci_open, + .nci_close = nfcmrvl_uart_nci_close, + .nci_send = nfcmrvl_uart_nci_send, + .nci_update_config = nfcmrvl_uart_nci_update_config +}; + +static int nfcmrvl_uart_parse_dt(struct device_node *node, + struct nfcmrvl_platform_data *pdata) +{ + struct device_node *matched_node; + int ret; + + matched_node = of_get_compatible_child(node, "marvell,nfc-uart"); + if (!matched_node) { + matched_node = of_get_compatible_child(node, "mrvl,nfc-uart"); + if (!matched_node) + return -ENODEV; + } + + ret = nfcmrvl_parse_dt(matched_node, pdata); + if (ret < 0) { + pr_err("Failed to get generic entries\n"); + of_node_put(matched_node); + return ret; + } + + if (of_find_property(matched_node, "flow-control", NULL)) + pdata->flow_control = 1; + else + pdata->flow_control = 0; + + if (of_find_property(matched_node, "break-control", NULL)) + pdata->break_control = 1; + else + pdata->break_control = 0; + + of_node_put(matched_node); + + return 0; +} + +/* +** NCI UART OPS +*/ + +static int nfcmrvl_nci_uart_open(struct nci_uart *nu) +{ + struct nfcmrvl_private *priv; + struct nfcmrvl_platform_data *pdata = NULL; + struct nfcmrvl_platform_data config; + struct device *dev = nu->tty->dev; + + /* + * Platform data cannot be used here since usually it is already used + * by low level serial driver. We can try to retrieve serial device + * and check if DT entries were added. + */ + + if (dev && dev->parent && dev->parent->of_node) + if (nfcmrvl_uart_parse_dt(dev->parent->of_node, &config) == 0) + pdata = &config; + + if (!pdata) { + pr_info("No platform data / DT -> fallback to module params\n"); + config.hci_muxed = hci_muxed; + config.reset_n_io = reset_n_io; + config.flow_control = flow_control; + config.break_control = break_control; + pdata = &config; + } + + priv = nfcmrvl_nci_register_dev(NFCMRVL_PHY_UART, nu, &uart_ops, + dev, pdata); + if (IS_ERR(priv)) + return PTR_ERR(priv); + + priv->support_fw_dnld = true; + + nu->drv_data = priv; + nu->ndev = priv->ndev; + + return 0; +} + +static void nfcmrvl_nci_uart_close(struct nci_uart *nu) +{ + nfcmrvl_nci_unregister_dev((struct nfcmrvl_private *)nu->drv_data); +} + +static int nfcmrvl_nci_uart_recv(struct nci_uart *nu, struct sk_buff *skb) +{ + return nfcmrvl_nci_recv_frame((struct nfcmrvl_private *)nu->drv_data, + skb); +} + +static void nfcmrvl_nci_uart_tx_start(struct nci_uart *nu) +{ + struct nfcmrvl_private *priv = (struct nfcmrvl_private *)nu->drv_data; + + if (priv->ndev->nfc_dev->fw_download_in_progress) + return; + + /* Remove BREAK to wake up the NFCC */ + if (priv->config.break_control && nu->tty->ops->break_ctl) { + nu->tty->ops->break_ctl(nu->tty, 0); + usleep_range(3000, 5000); + } +} + +static void nfcmrvl_nci_uart_tx_done(struct nci_uart *nu) +{ + struct nfcmrvl_private *priv = (struct nfcmrvl_private *)nu->drv_data; + + if (priv->ndev->nfc_dev->fw_download_in_progress) + return; + + /* + ** To ensure that if the NFCC goes in DEEP SLEEP sate we can wake him + ** up. we set BREAK. Once we will be ready to send again we will remove + ** it. + */ + if (priv->config.break_control && nu->tty->ops->break_ctl) { + nu->tty->ops->break_ctl(nu->tty, -1); + usleep_range(1000, 3000); + } +} + +static struct nci_uart nfcmrvl_nci_uart = { + .owner = THIS_MODULE, + .name = "nfcmrvl_uart", + .driver = NCI_UART_DRIVER_MARVELL, + .ops = { + .open = nfcmrvl_nci_uart_open, + .close = nfcmrvl_nci_uart_close, + .recv = nfcmrvl_nci_uart_recv, + .tx_start = nfcmrvl_nci_uart_tx_start, + .tx_done = nfcmrvl_nci_uart_tx_done, + } +}; + +/* +** Module init +*/ + +static int nfcmrvl_uart_init_module(void) +{ + return nci_uart_register(&nfcmrvl_nci_uart); +} + +static void nfcmrvl_uart_exit_module(void) +{ + nci_uart_unregister(&nfcmrvl_nci_uart); +} + +module_init(nfcmrvl_uart_init_module); +module_exit(nfcmrvl_uart_exit_module); + +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_DESCRIPTION("Marvell NFC-over-UART"); +MODULE_LICENSE("GPL v2"); + +module_param(flow_control, uint, 0); +MODULE_PARM_DESC(flow_control, "Tell if UART needs flow control at init."); + +module_param(break_control, uint, 0); +MODULE_PARM_DESC(break_control, "Tell if UART driver must drive break signal."); + +module_param(hci_muxed, uint, 0); +MODULE_PARM_DESC(hci_muxed, "Tell if transport is muxed in HCI one."); + +module_param(reset_n_io, int, 0); +MODULE_PARM_DESC(reset_n_io, "GPIO that is wired to RESET_N signal."); diff --git a/drivers/nfc/nfcmrvl/usb.c b/drivers/nfc/nfcmrvl/usb.c new file mode 100644 index 000000000..f26986eb5 --- /dev/null +++ b/drivers/nfc/nfcmrvl/usb.c @@ -0,0 +1,484 @@ +/** + * Marvell NFC-over-USB driver: USB interface related functions + * + * Copyright (C) 2014, 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/usb.h> +#include <linux/nfc.h> +#include <net/nfc/nci.h> +#include <net/nfc/nci_core.h> +#include "nfcmrvl.h" + +static struct usb_device_id nfcmrvl_table[] = { + { USB_DEVICE_AND_INTERFACE_INFO(0x1286, 0x2046, + USB_CLASS_VENDOR_SPEC, 4, 1) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, nfcmrvl_table); + +#define NFCMRVL_USB_BULK_RUNNING 1 +#define NFCMRVL_USB_SUSPENDING 2 + +struct nfcmrvl_usb_drv_data { + struct usb_device *udev; + struct usb_interface *intf; + unsigned long flags; + struct work_struct waker; + struct usb_anchor tx_anchor; + struct usb_anchor bulk_anchor; + struct usb_anchor deferred; + int tx_in_flight; + /* protects tx_in_flight */ + spinlock_t txlock; + struct usb_endpoint_descriptor *bulk_tx_ep; + struct usb_endpoint_descriptor *bulk_rx_ep; + int suspend_count; + struct nfcmrvl_private *priv; +}; + +static int nfcmrvl_inc_tx(struct nfcmrvl_usb_drv_data *drv_data) +{ + unsigned long flags; + int rv; + + spin_lock_irqsave(&drv_data->txlock, flags); + rv = test_bit(NFCMRVL_USB_SUSPENDING, &drv_data->flags); + if (!rv) + drv_data->tx_in_flight++; + spin_unlock_irqrestore(&drv_data->txlock, flags); + + return rv; +} + +static void nfcmrvl_bulk_complete(struct urb *urb) +{ + struct nfcmrvl_usb_drv_data *drv_data = urb->context; + struct sk_buff *skb; + int err; + + dev_dbg(&drv_data->udev->dev, "urb %p status %d count %d\n", + urb, urb->status, urb->actual_length); + + if (!test_bit(NFCMRVL_NCI_RUNNING, &drv_data->flags)) + return; + + if (!urb->status) { + skb = nci_skb_alloc(drv_data->priv->ndev, urb->actual_length, + GFP_ATOMIC); + if (!skb) { + nfc_err(&drv_data->udev->dev, "failed to alloc mem\n"); + } else { + skb_put_data(skb, urb->transfer_buffer, + urb->actual_length); + if (nfcmrvl_nci_recv_frame(drv_data->priv, skb) < 0) + nfc_err(&drv_data->udev->dev, + "corrupted Rx packet\n"); + } + } + + if (!test_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags)) + return; + + usb_anchor_urb(urb, &drv_data->bulk_anchor); + usb_mark_last_busy(drv_data->udev); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + /* -EPERM: urb is being killed; + * -ENODEV: device got disconnected + */ + if (err != -EPERM && err != -ENODEV) + nfc_err(&drv_data->udev->dev, + "urb %p failed to resubmit (%d)\n", urb, -err); + usb_unanchor_urb(urb); + } +} + +static int +nfcmrvl_submit_bulk_urb(struct nfcmrvl_usb_drv_data *drv_data, gfp_t mem_flags) +{ + struct urb *urb; + unsigned char *buf; + unsigned int pipe; + int err, size = NFCMRVL_NCI_MAX_EVENT_SIZE; + + if (!drv_data->bulk_rx_ep) + return -ENODEV; + + urb = usb_alloc_urb(0, mem_flags); + if (!urb) + return -ENOMEM; + + buf = kmalloc(size, mem_flags); + if (!buf) { + usb_free_urb(urb); + return -ENOMEM; + } + + pipe = usb_rcvbulkpipe(drv_data->udev, + drv_data->bulk_rx_ep->bEndpointAddress); + + usb_fill_bulk_urb(urb, drv_data->udev, pipe, buf, size, + nfcmrvl_bulk_complete, drv_data); + + urb->transfer_flags |= URB_FREE_BUFFER; + + usb_mark_last_busy(drv_data->udev); + usb_anchor_urb(urb, &drv_data->bulk_anchor); + + err = usb_submit_urb(urb, mem_flags); + if (err) { + if (err != -EPERM && err != -ENODEV) + nfc_err(&drv_data->udev->dev, + "urb %p submission failed (%d)\n", urb, -err); + usb_unanchor_urb(urb); + } + + usb_free_urb(urb); + + return err; +} + +static void nfcmrvl_tx_complete(struct urb *urb) +{ + struct sk_buff *skb = urb->context; + struct nci_dev *ndev = (struct nci_dev *)skb->dev; + struct nfcmrvl_private *priv = nci_get_drvdata(ndev); + struct nfcmrvl_usb_drv_data *drv_data = priv->drv_data; + unsigned long flags; + + nfc_info(priv->dev, "urb %p status %d count %d\n", + urb, urb->status, urb->actual_length); + + spin_lock_irqsave(&drv_data->txlock, flags); + drv_data->tx_in_flight--; + spin_unlock_irqrestore(&drv_data->txlock, flags); + + kfree(urb->setup_packet); + kfree_skb(skb); +} + +static int nfcmrvl_usb_nci_open(struct nfcmrvl_private *priv) +{ + struct nfcmrvl_usb_drv_data *drv_data = priv->drv_data; + int err; + + err = usb_autopm_get_interface(drv_data->intf); + if (err) + return err; + + drv_data->intf->needs_remote_wakeup = 1; + + err = nfcmrvl_submit_bulk_urb(drv_data, GFP_KERNEL); + if (err) + goto failed; + + set_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags); + nfcmrvl_submit_bulk_urb(drv_data, GFP_KERNEL); + + usb_autopm_put_interface(drv_data->intf); + return 0; + +failed: + usb_autopm_put_interface(drv_data->intf); + return err; +} + +static void nfcmrvl_usb_stop_traffic(struct nfcmrvl_usb_drv_data *drv_data) +{ + usb_kill_anchored_urbs(&drv_data->bulk_anchor); +} + +static int nfcmrvl_usb_nci_close(struct nfcmrvl_private *priv) +{ + struct nfcmrvl_usb_drv_data *drv_data = priv->drv_data; + int err; + + cancel_work_sync(&drv_data->waker); + + clear_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags); + + nfcmrvl_usb_stop_traffic(drv_data); + usb_kill_anchored_urbs(&drv_data->tx_anchor); + err = usb_autopm_get_interface(drv_data->intf); + if (err) + goto failed; + + drv_data->intf->needs_remote_wakeup = 0; + usb_autopm_put_interface(drv_data->intf); + +failed: + usb_scuttle_anchored_urbs(&drv_data->deferred); + return 0; +} + +static int nfcmrvl_usb_nci_send(struct nfcmrvl_private *priv, + struct sk_buff *skb) +{ + struct nfcmrvl_usb_drv_data *drv_data = priv->drv_data; + struct urb *urb; + unsigned int pipe; + int err; + + if (!drv_data->bulk_tx_ep) + return -ENODEV; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) + return -ENOMEM; + + pipe = usb_sndbulkpipe(drv_data->udev, + drv_data->bulk_tx_ep->bEndpointAddress); + + usb_fill_bulk_urb(urb, drv_data->udev, pipe, skb->data, skb->len, + nfcmrvl_tx_complete, skb); + + err = nfcmrvl_inc_tx(drv_data); + if (err) { + usb_anchor_urb(urb, &drv_data->deferred); + schedule_work(&drv_data->waker); + err = 0; + goto done; + } + + usb_anchor_urb(urb, &drv_data->tx_anchor); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + if (err != -EPERM && err != -ENODEV) + nfc_err(&drv_data->udev->dev, + "urb %p submission failed (%d)\n", urb, -err); + kfree(urb->setup_packet); + usb_unanchor_urb(urb); + } else { + usb_mark_last_busy(drv_data->udev); + } + +done: + usb_free_urb(urb); + return err; +} + +static struct nfcmrvl_if_ops usb_ops = { + .nci_open = nfcmrvl_usb_nci_open, + .nci_close = nfcmrvl_usb_nci_close, + .nci_send = nfcmrvl_usb_nci_send, +}; + +static void nfcmrvl_waker(struct work_struct *work) +{ + struct nfcmrvl_usb_drv_data *drv_data = + container_of(work, struct nfcmrvl_usb_drv_data, waker); + int err; + + err = usb_autopm_get_interface(drv_data->intf); + if (err) + return; + + usb_autopm_put_interface(drv_data->intf); +} + +static int nfcmrvl_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_endpoint_descriptor *ep_desc; + struct nfcmrvl_usb_drv_data *drv_data; + struct nfcmrvl_private *priv; + int i; + struct usb_device *udev = interface_to_usbdev(intf); + struct nfcmrvl_platform_data config; + + /* No configuration for USB */ + memset(&config, 0, sizeof(config)); + config.reset_n_io = -EINVAL; + + nfc_info(&udev->dev, "intf %p id %p\n", intf, id); + + drv_data = devm_kzalloc(&intf->dev, sizeof(*drv_data), GFP_KERNEL); + if (!drv_data) + return -ENOMEM; + + for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) { + ep_desc = &intf->cur_altsetting->endpoint[i].desc; + + if (!drv_data->bulk_tx_ep && + usb_endpoint_is_bulk_out(ep_desc)) { + drv_data->bulk_tx_ep = ep_desc; + continue; + } + + if (!drv_data->bulk_rx_ep && + usb_endpoint_is_bulk_in(ep_desc)) { + drv_data->bulk_rx_ep = ep_desc; + continue; + } + } + + if (!drv_data->bulk_tx_ep || !drv_data->bulk_rx_ep) + return -ENODEV; + + drv_data->udev = udev; + drv_data->intf = intf; + + INIT_WORK(&drv_data->waker, nfcmrvl_waker); + spin_lock_init(&drv_data->txlock); + + init_usb_anchor(&drv_data->tx_anchor); + init_usb_anchor(&drv_data->bulk_anchor); + init_usb_anchor(&drv_data->deferred); + + priv = nfcmrvl_nci_register_dev(NFCMRVL_PHY_USB, drv_data, &usb_ops, + &intf->dev, &config); + if (IS_ERR(priv)) + return PTR_ERR(priv); + + drv_data->priv = priv; + drv_data->priv->support_fw_dnld = false; + + usb_set_intfdata(intf, drv_data); + + return 0; +} + +static void nfcmrvl_disconnect(struct usb_interface *intf) +{ + struct nfcmrvl_usb_drv_data *drv_data = usb_get_intfdata(intf); + + if (!drv_data) + return; + + nfc_info(&drv_data->udev->dev, "intf %p\n", intf); + + nfcmrvl_nci_unregister_dev(drv_data->priv); + + usb_set_intfdata(drv_data->intf, NULL); +} + +#ifdef CONFIG_PM +static int nfcmrvl_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct nfcmrvl_usb_drv_data *drv_data = usb_get_intfdata(intf); + + nfc_info(&drv_data->udev->dev, "intf %p\n", intf); + + if (drv_data->suspend_count++) + return 0; + + spin_lock_irq(&drv_data->txlock); + if (!(PMSG_IS_AUTO(message) && drv_data->tx_in_flight)) { + set_bit(NFCMRVL_USB_SUSPENDING, &drv_data->flags); + spin_unlock_irq(&drv_data->txlock); + } else { + spin_unlock_irq(&drv_data->txlock); + drv_data->suspend_count--; + return -EBUSY; + } + + nfcmrvl_usb_stop_traffic(drv_data); + usb_kill_anchored_urbs(&drv_data->tx_anchor); + + return 0; +} + +static void nfcmrvl_play_deferred(struct nfcmrvl_usb_drv_data *drv_data) +{ + struct urb *urb; + int err; + + while ((urb = usb_get_from_anchor(&drv_data->deferred))) { + usb_anchor_urb(urb, &drv_data->tx_anchor); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + kfree(urb->setup_packet); + usb_unanchor_urb(urb); + usb_free_urb(urb); + break; + } + + drv_data->tx_in_flight++; + usb_free_urb(urb); + } + + /* Cleanup the rest deferred urbs. */ + while ((urb = usb_get_from_anchor(&drv_data->deferred))) { + kfree(urb->setup_packet); + usb_free_urb(urb); + } +} + +static int nfcmrvl_resume(struct usb_interface *intf) +{ + struct nfcmrvl_usb_drv_data *drv_data = usb_get_intfdata(intf); + int err = 0; + + nfc_info(&drv_data->udev->dev, "intf %p\n", intf); + + if (--drv_data->suspend_count) + return 0; + + if (!test_bit(NFCMRVL_NCI_RUNNING, &drv_data->flags)) + goto done; + + if (test_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags)) { + err = nfcmrvl_submit_bulk_urb(drv_data, GFP_NOIO); + if (err) { + clear_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags); + goto failed; + } + + nfcmrvl_submit_bulk_urb(drv_data, GFP_NOIO); + } + + spin_lock_irq(&drv_data->txlock); + nfcmrvl_play_deferred(drv_data); + clear_bit(NFCMRVL_USB_SUSPENDING, &drv_data->flags); + spin_unlock_irq(&drv_data->txlock); + + return 0; + +failed: + usb_scuttle_anchored_urbs(&drv_data->deferred); +done: + spin_lock_irq(&drv_data->txlock); + clear_bit(NFCMRVL_USB_SUSPENDING, &drv_data->flags); + spin_unlock_irq(&drv_data->txlock); + + return err; +} +#endif + +static struct usb_driver nfcmrvl_usb_driver = { + .name = "nfcmrvl", + .probe = nfcmrvl_probe, + .disconnect = nfcmrvl_disconnect, +#ifdef CONFIG_PM + .suspend = nfcmrvl_suspend, + .resume = nfcmrvl_resume, + .reset_resume = nfcmrvl_resume, +#endif + .id_table = nfcmrvl_table, + .supports_autosuspend = 1, + .disable_hub_initiated_lpm = 1, + .soft_unbind = 1, +}; +module_usb_driver(nfcmrvl_usb_driver); + +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_DESCRIPTION("Marvell NFC-over-USB driver"); +MODULE_LICENSE("GPL v2"); |