Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
# SPDX-License-Identifier: GPL-2.0-only
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.
+19
View File
@@ -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
+549
View File
@@ -0,0 +1,549 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Marvell NFC driver: Firmware downloader
*
* Copyright (C) 2015, Marvell International Ltd.
*/
#include <linux/module.h>
#include <linux/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)
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))
timer_delete_sync(&priv->ndev->cmd_timer);
if (timer_pending(&priv->fw_dnld.timer))
timer_delete_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 = timer_container_of(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,
const 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,
const 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,
const 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,
const 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,
const 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,
const 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,
const 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))
timer_delete_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;
strscpy(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;
}
+87
View File
@@ -0,0 +1,87 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Marvell NFC driver: Firmware downloader
*
* Copyright (C) 2015, Marvell International Ltd.
*/
#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
+274
View File
@@ -0,0 +1,274 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Marvell NFC-over-I2C driver: I2C interface related functions
*
* Copyright (C) 2015, Marvell International Ltd.
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/nfc.h>
#include <linux/delay.h>
#include <linux/of_irq.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;
}
*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)) {
kfree_skb(skb);
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;
}
if (ret) {
kfree_skb(skb);
return ret;
}
consume_skb(skb);
return 0;
}
static void nfcmrvl_i2c_nci_update_config(struct nfcmrvl_private *priv,
const void *param)
{
}
static const 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_property_read_bool(node, "i2c-int-falling"))
pdata->irq_polarity = IRQF_TRIGGER_FALLING;
else
pdata->irq_polarity = IRQF_TRIGGER_RISING;
ret = irq_of_parse_and_map(node, 0);
if (!ret) {
pr_err("Unable to get irq\n");
return -EINVAL;
}
pdata->irq = ret;
return 0;
}
static int nfcmrvl_i2c_probe(struct i2c_client *client)
{
const struct nfcmrvl_platform_data *pdata;
struct nfcmrvl_i2c_drv_data *drv_data;
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 void 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);
}
static const struct of_device_id of_nfcmrvl_i2c_match[] __maybe_unused = {
{ .compatible = "marvell,nfc-i2c", },
{},
};
MODULE_DEVICE_TABLE(of, of_nfcmrvl_i2c_match);
static const struct i2c_device_id nfcmrvl_i2c_id_table[] = {
{ .name = "nfcmrvl_i2c" },
{ }
};
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",
.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");
+257
View File
@@ -0,0 +1,257 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Marvell NFC driver: major functions
*
* Copyright (C) 2014-2015 Marvell International Ltd.
*/
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/of.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 const 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,
const struct nfcmrvl_if_ops *ops,
struct device *dev,
const struct nfcmrvl_platform_data *pdata)
{
struct nfcmrvl_private *priv;
int rc;
int headroom;
int tailroom;
u32 protocols;
priv = kzalloc_obj(*priv);
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 (!priv->config.reset_gpio) {
priv->config.reset_gpio =
devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(priv->config.reset_gpio)) {
priv->config.reset_gpio = NULL;
nfc_err(dev, "failed to get reset gpio\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;
}
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:
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);
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 (priv->config.reset_gpio) {
nfc_info(priv->dev, "reset the chip\n");
gpiod_set_value(priv->config.reset_gpio, 1);
usleep_range(5000, 10000);
gpiod_set_value(priv->config.reset_gpio, 0);
} else
nfc_info(priv->dev, "no reset available on this interface\n");
}
void nfcmrvl_chip_halt(struct nfcmrvl_private *priv)
{
if (priv->config.reset_gpio)
gpiod_set_value(priv->config.reset_gpio, 1);
}
int nfcmrvl_parse_dt(struct device_node *node,
struct nfcmrvl_platform_data *pdata)
{
pdata->reset_gpio = NULL;
pdata->hci_muxed = of_property_read_bool(node, "hci-muxed");
return 0;
}
EXPORT_SYMBOL_GPL(nfcmrvl_parse_dt);
MODULE_AUTHOR("Marvell International Ltd.");
MODULE_DESCRIPTION("Marvell NFC driver");
MODULE_LICENSE("GPL v2");
+134
View File
@@ -0,0 +1,134 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Marvell NFC driver
*
* Copyright (C) 2014-2015, Marvell International Ltd.
*/
#ifndef _NFCMRVL_H_
#define _NFCMRVL_H_
#include "fw_dnld.h"
struct gpio_desc;
/* 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 Parameters
*/
#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_platform_data {
/*
* Generic
*/
/* GPIO that is wired to RESET_N signal */
struct gpio_desc *reset_gpio;
/* Tell if transport is muxed in HCI one */
bool hci_muxed;
/*
* UART specific
*/
/* Tell if UART needs flow control at init */
bool flow_control;
/* Tell if firmware supports break control for power management */
bool break_control;
/*
* I2C specific
*/
unsigned int irq;
unsigned int irq_polarity;
};
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 */
const 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,
const struct nfcmrvl_if_ops *ops,
struct device *dev,
const 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
+210
View File
@@ -0,0 +1,210 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Marvell NFC-over-SPI driver: SPI interface related functions
*
* Copyright (C) 2015, Marvell International Ltd.
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/nfc.h>
#include <linux/of_irq.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 const 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) {
pr_err("Unable to get irq\n");
return -EINVAL;
}
pdata->irq = ret;
return 0;
}
static int nfcmrvl_spi_probe(struct spi_device *spi)
{
const 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 void 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);
}
static const struct of_device_id of_nfcmrvl_spi_match[] __maybe_unused = {
{ .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",
.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");
+215
View File
@@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Marvell NFC-over-UART driver
*
* Copyright (C) 2015, Marvell International Ltd.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/printk.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;
/*
* 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 const 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 *dev)
{
struct device_node *matched_node;
struct gpio_desc *reset_gpio;
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;
}
pdata->flow_control = of_property_read_bool(matched_node, "flow-control");
pdata->break_control = of_property_read_bool(matched_node, "break-control");
reset_gpio = devm_fwnode_gpiod_get_optional(dev,
of_fwnode_handle(matched_node),
"reset", GPIOD_OUT_HIGH,
"nfcmrvl_reset_n");
if (IS_ERR(reset_gpio)) {
of_node_put(matched_node);
return PTR_ERR(reset_gpio);
}
pdata->reset_gpio = reset_gpio;
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 config;
const struct nfcmrvl_platform_data *pdata = NULL;
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, dev) == 0)
pdata = &config;
if (!pdata) {
pr_info("No platform data / DT -> fallback to module params\n");
config.hci_muxed = hci_muxed;
config.reset_gpio = NULL;
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_driver(nfcmrvl_nci_uart, nci_uart_register, nci_uart_unregister);
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.");
+459
View File
@@ -0,0 +1,459 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Marvell NFC-over-USB driver: USB interface related functions
*
* Copyright (C) 2014, Marvell International Ltd.
*/
#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;
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) {
struct sk_buff *skb;
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 const 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 nfcmrvl_usb_drv_data *drv_data;
struct nfcmrvl_private *priv;
struct usb_device *udev = interface_to_usbdev(intf);
struct nfcmrvl_platform_data config;
int ret;
/* No configuration for USB */
memset(&config, 0, sizeof(config));
config.reset_gpio = NULL;
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;
ret = usb_find_common_endpoints(intf->cur_altsetting, &drv_data->bulk_rx_ep,
&drv_data->bulk_tx_ep, NULL, NULL);
if (ret)
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");