NFC 4.2 pull request
This is the NFC pull request for 4.2. - NCI drivers can now define their own handlers for processing proprietary NCI responses and notifications. - NFC vendors can use a dedicated netlink API to send their own proprietary commands, like e.g. all commands needed to implement vendor specific manufacturing tools. - A new generic NCI over UART driver against which any NCI chipset running on top of a serial interface can register. - The st21nfcb driver is renamed to st-nci as it can and will support most of ST Microelectronics NCI chipsets. - The st21nfcb driver can put its CLF in hibernate mode and save significant amount of power. - A few st21nfcb minor fixes. - The NXP NCI driver now supports ACPI enumeration. - The Marvell NCI driver now supports both USB and serial physical interfaces. - The Marvell NCI drivers also supports NCI frames being muxed over HCI. This is a setting that can be defined by a DT property. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJVe2k1AAoJEIqAPN1PVmxKt88P/11r4VVq57Kh4BHKTa6CAs5m FBMmQdGlpU+O8VUjQLl7Y+GWoVTmDOUm/uKd6xWIvgBl4/X+UKwhNVldpsWXvw1t cTnn0BykvvfA4FOQJBqgGTC38oC04REr4uTK3+NnjE6sBpQ86Ljfk7xarMKdDpKI TKchY4sIOHIHXHVPVjW4fyEVF6pUduTenH73zWPkyKZgOQaSbgR75j12WMEI20kw POykX9t6UcPDzcJ+doktUgfxhHB1YlML7Z5xNrIkbk4kyAj140Ds9mzEEBllyivc xX1Cy6h3S+vrnx/CnNa85nA/y7cH0oK8NVQwmYicqKT/W7wASF7JZYLT6A7E81c1 zLGHWVEK0awS/KM+bLI3ixQVNWFDqYPM8R36Ag0NotUZJPKiHRQkyBU3VSS8XT2f HRBlYgNYSKfR1m9LCPtm+sq6psP7cnvRAfzDrZuWtyqctriZKqCuOXbVAHJtb/3s gHxMkfyTL1zRW7u/xGid15JiicNAJd2aQmkHzZJCnIgUyTrnvhSNX6sRjBZ6iQCf z3+aTIaNEApOZ2qtfOrnSTtW0wjOBdpUK3hlSX5ozQ+V6dspoN0ld1JWURQPqkzu jWwCONO29/9yuoc/qTbWPGL4avAqyCFEzhAk/Ux19jIqoXNzHVF9f4HICc1aRLTf TKpcrQPYB61fU07CV9uT =+IG2 -----END PGP SIGNATURE----- Merge tag 'nfc-next-4.2-1' of git://git.kernel.org/pub/scm/linux/kernel/git/sameo/nfc-next Samuel Ortiz says: ==================== NFC 4.2 pull request This is the NFC pull request for 4.2. - NCI drivers can now define their own handlers for processing proprietary NCI responses and notifications. - NFC vendors can use a dedicated netlink API to send their own proprietary commands, like e.g. all commands needed to implement vendor specific manufacturing tools. - A new generic NCI over UART driver against which any NCI chipset running on top of a serial interface can register. - The st21nfcb driver is renamed to st-nci as it can and will support most of ST Microelectronics NCI chipsets. - The st21nfcb driver can put its CLF in hibernate mode and save significant amount of power. - A few st21nfcb minor fixes. - The NXP NCI driver now supports ACPI enumeration. - The Marvell NCI driver now supports both USB and serial physical interfaces. - The Marvell NCI drivers also supports NCI frames being muxed over HCI. This is a setting that can be defined by a DT property. ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
023033b1ec
29
Documentation/devicetree/bindings/net/nfc/nfcmrvl.txt
Normal file
29
Documentation/devicetree/bindings/net/nfc/nfcmrvl.txt
Normal file
@ -0,0 +1,29 @@
|
||||
* Marvell International Ltd. NCI NFC Controller
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "mrvl,nfc-uart".
|
||||
|
||||
Optional SoC specific properties:
|
||||
- pinctrl-names: Contains only one value - "default".
|
||||
- pintctrl-0: Specifies the pin control groups used for this controller.
|
||||
- reset-n-io: Output GPIO pin used to reset the chip (active low).
|
||||
- hci-muxed: Specifies that the chip is muxing NCI over HCI frames.
|
||||
|
||||
Optional UART-based chip specific properties:
|
||||
- flow-control: Specifies that the chip is using RTS/CTS.
|
||||
- break-control: Specifies that the chip needs specific break management.
|
||||
|
||||
Example (for ARM-based BeagleBoard Black with 88W8887 on UART5):
|
||||
|
||||
&uart5 {
|
||||
status = "okay";
|
||||
|
||||
nfcmrvluart: nfcmrvluart@5 {
|
||||
compatible = "mrvl,nfc-uart";
|
||||
|
||||
reset-n-io = <&gpio3 16 0>;
|
||||
|
||||
hci-muxed;
|
||||
flow-control;
|
||||
}
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
* STMicroelectronics SAS. ST21NFCB NFC Controller
|
||||
* STMicroelectronics SAS. ST NCI NFC Controller
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "st,st21nfcb-i2c".
|
||||
- compatible: Should be "st,st21nfcb-i2c" or "st,st21nfcc-i2c".
|
||||
- clock-frequency: I²C work frequency.
|
||||
- reg: address on the bus
|
||||
- interrupt-parent: phandle for the interrupt gpio controller
|
@ -18,6 +18,9 @@ Optional SoC Specific Properties:
|
||||
"IRQ Status Read" erratum.
|
||||
- en2-rf-quirk: Specify that the trf7970a being used has the "EN2 RF"
|
||||
erratum.
|
||||
- t5t-rmb-extra-byte-quirk: Specify that the trf7970a has the erratum
|
||||
where an extra byte is returned by Read Multiple Block commands issued
|
||||
to Type 5 tags.
|
||||
|
||||
Example (for ARM-based BeagleBone with TRF7970A on SPI1):
|
||||
|
||||
@ -39,6 +42,7 @@ Example (for ARM-based BeagleBone with TRF7970A on SPI1):
|
||||
autosuspend-delay = <30000>;
|
||||
irq-status-read-quirk;
|
||||
en2-rf-quirk;
|
||||
t5t-rmb-extra-byte-quirk;
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
|
@ -122,7 +122,7 @@ This must be done from a context that can sleep.
|
||||
PHY Management
|
||||
--------------
|
||||
|
||||
The physical link (i2c, ...) management is defined by the following struture:
|
||||
The physical link (i2c, ...) management is defined by the following structure:
|
||||
|
||||
struct nfc_phy_ops {
|
||||
int (*write)(void *dev_id, struct sk_buff *skb);
|
||||
|
@ -72,6 +72,6 @@ source "drivers/nfc/pn544/Kconfig"
|
||||
source "drivers/nfc/microread/Kconfig"
|
||||
source "drivers/nfc/nfcmrvl/Kconfig"
|
||||
source "drivers/nfc/st21nfca/Kconfig"
|
||||
source "drivers/nfc/st21nfcb/Kconfig"
|
||||
source "drivers/nfc/st-nci/Kconfig"
|
||||
source "drivers/nfc/nxp-nci/Kconfig"
|
||||
endmenu
|
||||
|
@ -12,7 +12,5 @@ obj-$(CONFIG_NFC_PORT100) += port100.o
|
||||
obj-$(CONFIG_NFC_MRVL) += nfcmrvl/
|
||||
obj-$(CONFIG_NFC_TRF7970A) += trf7970a.o
|
||||
obj-$(CONFIG_NFC_ST21NFCA) += st21nfca/
|
||||
obj-$(CONFIG_NFC_ST21NFCB) += st21nfcb/
|
||||
obj-$(CONFIG_NFC_ST_NCI) += st-nci/
|
||||
obj-$(CONFIG_NFC_NXP_NCI) += nxp-nci/
|
||||
|
||||
ccflags-$(CONFIG_NFC_DEBUG) := -DDEBUG
|
||||
|
@ -211,7 +211,6 @@ static int microread_i2c_read(struct microread_i2c_phy *phy,
|
||||
static irqreturn_t microread_i2c_irq_thread_fn(int irq, void *phy_id)
|
||||
{
|
||||
struct microread_i2c_phy *phy = phy_id;
|
||||
struct i2c_client *client;
|
||||
struct sk_buff *skb = NULL;
|
||||
int r;
|
||||
|
||||
@ -220,8 +219,6 @@ static irqreturn_t microread_i2c_irq_thread_fn(int irq, void *phy_id)
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
client = phy->i2c_dev;
|
||||
|
||||
if (phy->hard_fault != 0)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
|
@ -21,3 +21,14 @@ config NFC_MRVL_USB
|
||||
|
||||
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_MRVL && NFC_NCI_UART
|
||||
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.
|
||||
|
@ -7,3 +7,6 @@ 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
|
||||
|
@ -17,6 +17,9 @@
|
||||
*/
|
||||
|
||||
#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>
|
||||
@ -63,20 +66,25 @@ static int nfcmrvl_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
|
||||
if (!test_bit(NFCMRVL_NCI_RUNNING, &priv->flags))
|
||||
return -EBUSY;
|
||||
|
||||
if (priv->config.hci_muxed) {
|
||||
unsigned char *hdr;
|
||||
unsigned char len = skb->len;
|
||||
|
||||
hdr = (char *) 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;
|
||||
|
||||
val = NFCMRVL_GPIO_PIN_NFC_NOT_ALLOWED;
|
||||
nci_set_config(ndev, NFCMRVL_NOT_ALLOWED_ID, 1, &val);
|
||||
val = NFCMRVL_GPIO_PIN_NFC_ACTIVE;
|
||||
nci_set_config(ndev, NFCMRVL_ACTIVE_ID, 1, &val);
|
||||
val = NFCMRVL_EXT_COEX_ENABLE;
|
||||
nci_set_config(ndev, NFCMRVL_EXT_COEX_ID, 1, &val);
|
||||
__u8 val = 1;
|
||||
|
||||
nci_set_config(ndev, NFCMRVL_PB_BAIL_OUT, 1, &val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -88,11 +96,13 @@ static struct nci_ops nfcmrvl_nci_ops = {
|
||||
};
|
||||
|
||||
struct nfcmrvl_private *nfcmrvl_nci_register_dev(void *drv_data,
|
||||
struct nfcmrvl_if_ops *ops,
|
||||
struct device *dev)
|
||||
struct nfcmrvl_if_ops *ops,
|
||||
struct device *dev,
|
||||
struct nfcmrvl_platform_data *pdata)
|
||||
{
|
||||
struct nfcmrvl_private *priv;
|
||||
int rc;
|
||||
int headroom = 0;
|
||||
u32 protocols;
|
||||
|
||||
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
||||
@ -103,13 +113,30 @@ struct nfcmrvl_private *nfcmrvl_nci_register_dev(void *drv_data,
|
||||
priv->if_ops = ops;
|
||||
priv->dev = dev;
|
||||
|
||||
memcpy(&priv->config, pdata, sizeof(*pdata));
|
||||
|
||||
if (priv->config.reset_n_io) {
|
||||
rc = devm_gpio_request_one(dev,
|
||||
priv->config.reset_n_io,
|
||||
GPIOF_OUT_INIT_LOW,
|
||||
"nfcmrvl_reset_n");
|
||||
if (rc < 0)
|
||||
nfc_err(dev, "failed to request reset_n io\n");
|
||||
}
|
||||
|
||||
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_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, 0, 0);
|
||||
priv->ndev = nci_allocate_device(&nfcmrvl_nci_ops, protocols,
|
||||
headroom, 0);
|
||||
if (!priv->ndev) {
|
||||
nfc_err(dev, "nci_allocate_device failed\n");
|
||||
rc = -ENOMEM;
|
||||
@ -118,6 +145,8 @@ struct nfcmrvl_private *nfcmrvl_nci_register_dev(void *drv_data,
|
||||
|
||||
nci_set_drvdata(priv->ndev, priv);
|
||||
|
||||
nfcmrvl_chip_reset(priv);
|
||||
|
||||
rc = nci_register_device(priv->ndev);
|
||||
if (rc) {
|
||||
nfc_err(dev, "nci_register_device failed %d\n", rc);
|
||||
@ -144,21 +173,84 @@ void nfcmrvl_nci_unregister_dev(struct nfcmrvl_private *priv)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nfcmrvl_nci_unregister_dev);
|
||||
|
||||
int nfcmrvl_nci_recv_frame(struct nfcmrvl_private *priv, void *data, int count)
|
||||
int nfcmrvl_nci_recv_frame(struct nfcmrvl_private *priv, struct sk_buff *skb)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
skb = nci_skb_alloc(priv->ndev, count, GFP_ATOMIC);
|
||||
if (!skb)
|
||||
return -ENOMEM;
|
||||
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;
|
||||
}
|
||||
|
||||
memcpy(skb_put(skb, count), data, count);
|
||||
nci_recv_frame(priv->ndev, skb);
|
||||
|
||||
return count;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nfcmrvl_nci_recv_frame);
|
||||
|
||||
void nfcmrvl_chip_reset(struct nfcmrvl_private *priv)
|
||||
{
|
||||
/*
|
||||
* This function does not take care if someone is using the device.
|
||||
* To be improved.
|
||||
*/
|
||||
|
||||
if (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");
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
|
||||
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");
|
||||
reset_n_io = 0;
|
||||
} 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;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int nfcmrvl_parse_dt(struct device_node *node,
|
||||
struct nfcmrvl_platform_data *pdata)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
#endif
|
||||
EXPORT_SYMBOL_GPL(nfcmrvl_parse_dt);
|
||||
|
||||
MODULE_AUTHOR("Marvell International Ltd.");
|
||||
MODULE_DESCRIPTION("Marvell NFC driver ver " VERSION);
|
||||
MODULE_VERSION(VERSION);
|
||||
|
@ -16,6 +16,11 @@
|
||||
* this warranty disclaimer.
|
||||
**/
|
||||
|
||||
#ifndef _NFCMRVL_H_
|
||||
#define _NFCMRVL_H_
|
||||
|
||||
#include <linux/platform_data/nfcmrvl.h>
|
||||
|
||||
/* Define private flags: */
|
||||
#define NFCMRVL_NCI_RUNNING 1
|
||||
|
||||
@ -27,11 +32,49 @@
|
||||
#define NFCMRVL_GPIO_PIN_NFC_ACTIVE 0xB
|
||||
#define NFCMRVL_NCI_MAX_EVENT_SIZE 260
|
||||
|
||||
/*
|
||||
** NCI FW Parmaters
|
||||
*/
|
||||
|
||||
#define NFCMRVL_PB_BAIL_OUT 0x11
|
||||
|
||||
/*
|
||||
** 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,
|
||||
};
|
||||
|
||||
|
||||
struct nfcmrvl_private {
|
||||
struct nci_dev *ndev;
|
||||
|
||||
unsigned long flags;
|
||||
|
||||
/* Platform configuration */
|
||||
struct nfcmrvl_platform_data config;
|
||||
|
||||
struct nci_dev *ndev;
|
||||
|
||||
/*
|
||||
** 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;
|
||||
};
|
||||
|
||||
@ -42,7 +85,16 @@ struct nfcmrvl_if_ops {
|
||||
};
|
||||
|
||||
void nfcmrvl_nci_unregister_dev(struct nfcmrvl_private *priv);
|
||||
int nfcmrvl_nci_recv_frame(struct nfcmrvl_private *priv, void *data, int count);
|
||||
int nfcmrvl_nci_recv_frame(struct nfcmrvl_private *priv, struct sk_buff *skb);
|
||||
struct nfcmrvl_private *nfcmrvl_nci_register_dev(void *drv_data,
|
||||
struct nfcmrvl_if_ops *ops,
|
||||
struct device *dev);
|
||||
struct nfcmrvl_if_ops *ops,
|
||||
struct device *dev,
|
||||
struct nfcmrvl_platform_data *pdata);
|
||||
|
||||
|
||||
void nfcmrvl_chip_reset(struct nfcmrvl_private *priv);
|
||||
|
||||
int nfcmrvl_parse_dt(struct device_node *node,
|
||||
struct nfcmrvl_platform_data *pdata);
|
||||
|
||||
#endif
|
||||
|
225
drivers/nfc/nfcmrvl/uart.c
Normal file
225
drivers/nfc/nfcmrvl/uart.c
Normal file
@ -0,0 +1,225 @@
|
||||
/**
|
||||
* 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 unsigned int reset_n_io;
|
||||
|
||||
/*
|
||||
** 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 struct nfcmrvl_if_ops uart_ops = {
|
||||
.nci_open = nfcmrvl_uart_nci_open,
|
||||
.nci_close = nfcmrvl_uart_nci_close,
|
||||
.nci_send = nfcmrvl_uart_nci_send,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
|
||||
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_find_compatible_node(node, NULL, "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");
|
||||
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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static int nfcmrvl_uart_parse_dt(struct device_node *node,
|
||||
struct nfcmrvl_platform_data *pdata)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
** 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;
|
||||
|
||||
/*
|
||||
* 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 (nu->tty->dev->parent && nu->tty->dev->parent->of_node)
|
||||
if (nfcmrvl_uart_parse_dt(nu->tty->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(nu, &uart_ops, nu->tty->dev, pdata);
|
||||
if (IS_ERR(priv))
|
||||
return PTR_ERR(priv);
|
||||
|
||||
priv->phy = NFCMRVL_PHY_UART;
|
||||
|
||||
nu->drv_data = priv;
|
||||
nu->ndev = priv->ndev;
|
||||
|
||||
/* Set BREAK */
|
||||
if (priv->config.break_control && nu->tty->ops->break_ctl)
|
||||
nu->tty->ops->break_ctl(nu->tty, -1);
|
||||
|
||||
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;
|
||||
|
||||
/* 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;
|
||||
|
||||
/*
|
||||
** 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);
|
||||
}
|
||||
|
||||
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, uint, 0);
|
||||
MODULE_PARM_DESC(reset_n_io, "GPIO that is wired to RESET_N signal.");
|
@ -26,7 +26,8 @@
|
||||
#define VERSION "1.0"
|
||||
|
||||
static struct usb_device_id nfcmrvl_table[] = {
|
||||
{ USB_DEVICE_INTERFACE_CLASS(0x1286, 0x2046, 0xff) },
|
||||
{ USB_DEVICE_AND_INTERFACE_INFO(0x1286, 0x2046,
|
||||
USB_CLASS_VENDOR_SPEC, 4, 1) },
|
||||
{ } /* Terminating entry */
|
||||
};
|
||||
|
||||
@ -69,18 +70,27 @@ static int nfcmrvl_inc_tx(struct nfcmrvl_usb_drv_data *drv_data)
|
||||
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",
|
||||
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) {
|
||||
if (nfcmrvl_nci_recv_frame(drv_data->priv, urb->transfer_buffer,
|
||||
urb->actual_length) < 0)
|
||||
nfc_err(&drv_data->udev->dev, "corrupted Rx packet\n");
|
||||
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 {
|
||||
memcpy(skb_put(skb, urb->actual_length),
|
||||
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))
|
||||
@ -292,6 +302,10 @@ static int nfcmrvl_probe(struct usb_interface *intf,
|
||||
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));
|
||||
|
||||
nfc_info(&udev->dev, "intf %p id %p\n", intf, id);
|
||||
|
||||
@ -329,11 +343,12 @@ static int nfcmrvl_probe(struct usb_interface *intf,
|
||||
init_usb_anchor(&drv_data->deferred);
|
||||
|
||||
priv = nfcmrvl_nci_register_dev(drv_data, &usb_ops,
|
||||
&drv_data->udev->dev);
|
||||
&drv_data->udev->dev, &config);
|
||||
if (IS_ERR(priv))
|
||||
return PTR_ERR(priv);
|
||||
|
||||
drv_data->priv = priv;
|
||||
drv_data->priv->phy = NFCMRVL_PHY_USB;
|
||||
priv->dev = &drv_data->udev->dev;
|
||||
|
||||
usb_set_intfdata(intf, drv_data);
|
||||
|
@ -7,5 +7,3 @@ nxp-nci_i2c-objs = i2c.o
|
||||
|
||||
obj-$(CONFIG_NFC_NXP_NCI) += nxp-nci.o
|
||||
obj-$(CONFIG_NFC_NXP_NCI_I2C) += nxp-nci_i2c.o
|
||||
|
||||
ccflags-$(CONFIG_NFC_DEBUG) := -DDEBUG
|
||||
|
@ -2,8 +2,10 @@
|
||||
* I2C link layer for the NXP NCI driver
|
||||
*
|
||||
* Copyright (C) 2014 NXP Semiconductors All rights reserved.
|
||||
* Copyright (C) 2012-2015 Intel Corporation. All rights reserved.
|
||||
*
|
||||
* Authors: Clément Perrochaud <clement.perrochaud@nxp.com>
|
||||
* Authors: Oleg Zhurakivskyy <oleg.zhurakivskyy@intel.com>
|
||||
*
|
||||
* Derived from PN544 device driver:
|
||||
* Copyright (C) 2012 Intel Corporation. All rights reserved.
|
||||
@ -23,12 +25,14 @@
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/nfc.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/platform_data/nxp-nci.h>
|
||||
@ -48,6 +52,7 @@ struct nxp_nci_i2c_phy {
|
||||
|
||||
unsigned int gpio_en;
|
||||
unsigned int gpio_fw;
|
||||
unsigned int gpio_irq;
|
||||
|
||||
int hard_fault; /*
|
||||
* < 0 if hardware error occurred (e.g. i2c err)
|
||||
@ -308,6 +313,37 @@ static int nxp_nci_i2c_parse_devtree(struct i2c_client *client)
|
||||
|
||||
#endif
|
||||
|
||||
static int nxp_nci_i2c_acpi_config(struct nxp_nci_i2c_phy *phy)
|
||||
{
|
||||
struct i2c_client *client = phy->i2c_dev;
|
||||
struct gpio_desc *gpiod_en, *gpiod_fw, *gpiod_irq;
|
||||
|
||||
gpiod_en = devm_gpiod_get_index(&client->dev, NULL, 2);
|
||||
gpiod_fw = devm_gpiod_get_index(&client->dev, NULL, 1);
|
||||
gpiod_irq = devm_gpiod_get_index(&client->dev, NULL, 0);
|
||||
|
||||
if (IS_ERR(gpiod_en) || IS_ERR(gpiod_fw) || IS_ERR(gpiod_irq)) {
|
||||
nfc_err(&client->dev, "No GPIOs\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
gpiod_direction_output(gpiod_en, 0);
|
||||
gpiod_direction_output(gpiod_fw, 0);
|
||||
gpiod_direction_input(gpiod_irq);
|
||||
|
||||
client->irq = gpiod_to_irq(gpiod_irq);
|
||||
if (client->irq < 0) {
|
||||
nfc_err(&client->dev, "No IRQ\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
phy->gpio_en = desc_to_gpio(gpiod_en);
|
||||
phy->gpio_fw = desc_to_gpio(gpiod_fw);
|
||||
phy->gpio_irq = desc_to_gpio(gpiod_irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nxp_nci_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
@ -343,6 +379,11 @@ static int nxp_nci_i2c_probe(struct i2c_client *client,
|
||||
phy->gpio_en = pdata->gpio_en;
|
||||
phy->gpio_fw = pdata->gpio_fw;
|
||||
client->irq = pdata->irq;
|
||||
} else if (ACPI_HANDLE(&client->dev)) {
|
||||
r = nxp_nci_i2c_acpi_config(phy);
|
||||
if (r < 0)
|
||||
goto probe_exit;
|
||||
goto nci_probe;
|
||||
} else {
|
||||
nfc_err(&client->dev, "No platform data\n");
|
||||
r = -EINVAL;
|
||||
@ -359,6 +400,7 @@ static int nxp_nci_i2c_probe(struct i2c_client *client,
|
||||
if (r < 0)
|
||||
goto probe_exit;
|
||||
|
||||
nci_probe:
|
||||
r = nxp_nci_probe(phy, &client->dev, &i2c_phy_ops,
|
||||
NXP_NCI_I2C_MAX_PAYLOAD, &phy->ndev);
|
||||
if (r < 0)
|
||||
@ -397,10 +439,19 @@ static const struct of_device_id of_nxp_nci_i2c_match[] = {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_nxp_nci_i2c_match);
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
static struct acpi_device_id acpi_id[] = {
|
||||
{ "NXP7471" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, acpi_id);
|
||||
#endif
|
||||
|
||||
static struct i2c_driver nxp_nci_i2c_driver = {
|
||||
.driver = {
|
||||
.name = NXP_NCI_I2C_DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.acpi_match_table = ACPI_PTR(acpi_id),
|
||||
.of_match_table = of_match_ptr(of_nxp_nci_i2c_match),
|
||||
},
|
||||
.probe = nxp_nci_i2c_probe,
|
||||
@ -413,3 +464,4 @@ module_i2c_driver(nxp_nci_i2c_driver);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("I2C driver for NXP NCI NFC controllers");
|
||||
MODULE_AUTHOR("Clément Perrochaud <clement.perrochaud@nxp.com>");
|
||||
MODULE_AUTHOR("Oleg Zhurakivskyy <oleg.zhurakivskyy@intel.com>");
|
||||
|
@ -895,56 +895,35 @@ static int pn544_hci_i2c_acpi_request_resources(struct i2c_client *client)
|
||||
return -ENODEV;
|
||||
|
||||
/* Get EN GPIO from ACPI */
|
||||
gpiod_en = devm_gpiod_get_index(dev, PN544_GPIO_NAME_EN, 1);
|
||||
gpiod_en = devm_gpiod_get_index(dev, PN544_GPIO_NAME_EN, 1,
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(gpiod_en)) {
|
||||
nfc_err(dev,
|
||||
"Unable to get EN GPIO\n");
|
||||
nfc_err(dev, "Unable to get EN GPIO\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
phy->gpio_en = desc_to_gpio(gpiod_en);
|
||||
|
||||
/* Configuration EN GPIO */
|
||||
ret = gpiod_direction_output(gpiod_en, 0);
|
||||
if (ret) {
|
||||
nfc_err(dev, "Fail EN pin direction\n");
|
||||
return ret;
|
||||
}
|
||||
phy->gpio_en = desc_to_gpio(gpiod_en);
|
||||
|
||||
/* Get FW GPIO from ACPI */
|
||||
gpiod_fw = devm_gpiod_get_index(dev, PN544_GPIO_NAME_FW, 2);
|
||||
gpiod_fw = devm_gpiod_get_index(dev, PN544_GPIO_NAME_FW, 2,
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(gpiod_fw)) {
|
||||
nfc_err(dev,
|
||||
"Unable to get FW GPIO\n");
|
||||
nfc_err(dev, "Unable to get FW GPIO\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
phy->gpio_fw = desc_to_gpio(gpiod_fw);
|
||||
|
||||
/* Configuration FW GPIO */
|
||||
ret = gpiod_direction_output(gpiod_fw, 0);
|
||||
if (ret) {
|
||||
nfc_err(dev, "Fail FW pin direction\n");
|
||||
return ret;
|
||||
}
|
||||
phy->gpio_fw = desc_to_gpio(gpiod_fw);
|
||||
|
||||
/* Get IRQ GPIO */
|
||||
gpiod_irq = devm_gpiod_get_index(dev, PN544_GPIO_NAME_IRQ, 0);
|
||||
gpiod_irq = devm_gpiod_get_index(dev, PN544_GPIO_NAME_IRQ, 0,
|
||||
GPIOD_IN);
|
||||
if (IS_ERR(gpiod_irq)) {
|
||||
nfc_err(dev,
|
||||
"Unable to get IRQ GPIO\n");
|
||||
nfc_err(dev, "Unable to get IRQ GPIO\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
phy->gpio_irq = desc_to_gpio(gpiod_irq);
|
||||
|
||||
/* Configure IRQ GPIO */
|
||||
ret = gpiod_direction_input(gpiod_irq);
|
||||
if (ret) {
|
||||
nfc_err(dev, "Fail IRQ pin direction\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Map the pin to an IRQ */
|
||||
ret = gpiod_to_irq(gpiod_irq);
|
||||
if (ret < 0) {
|
||||
|
23
drivers/nfc/st-nci/Kconfig
Normal file
23
drivers/nfc/st-nci/Kconfig
Normal file
@ -0,0 +1,23 @@
|
||||
config NFC_ST_NCI
|
||||
tristate "STMicroelectronics ST NCI NFC driver"
|
||||
depends on NFC_NCI
|
||||
default n
|
||||
---help---
|
||||
STMicroelectronics NFC NCI chips core driver. It implements the chipset
|
||||
NCI logic and hooks into the NFC kernel APIs. Physical layers will
|
||||
register against it.
|
||||
|
||||
To compile this driver as a module, choose m here. The module will
|
||||
be called st-nci.
|
||||
Say N if unsure.
|
||||
|
||||
config NFC_ST_NCI_I2C
|
||||
tristate "NFC ST NCI i2c support"
|
||||
depends on NFC_ST_NCI && I2C
|
||||
---help---
|
||||
This module adds support for an I2C interface to the
|
||||
STMicroelectronics NFC NCI chips familly.
|
||||
Select this if your platform is using the i2c bus.
|
||||
|
||||
If you choose to build a module, it'll be called st-nci_i2c.
|
||||
Say N if unsure.
|
9
drivers/nfc/st-nci/Makefile
Normal file
9
drivers/nfc/st-nci/Makefile
Normal file
@ -0,0 +1,9 @@
|
||||
#
|
||||
# Makefile for ST21NFCB NCI based NFC driver
|
||||
#
|
||||
|
||||
st-nci-objs = ndlc.o core.o st-nci_se.o
|
||||
obj-$(CONFIG_NFC_ST_NCI) += st-nci.o
|
||||
|
||||
st-nci_i2c-objs = i2c.o
|
||||
obj-$(CONFIG_NFC_ST_NCI_I2C) += st-nci_i2c.o
|
179
drivers/nfc/st-nci/core.c
Normal file
179
drivers/nfc/st-nci/core.c
Normal file
@ -0,0 +1,179 @@
|
||||
/*
|
||||
* NCI based Driver for STMicroelectronics NFC Chip
|
||||
*
|
||||
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/nfc.h>
|
||||
#include <net/nfc/nci.h>
|
||||
#include <net/nfc/nci_core.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "st-nci.h"
|
||||
#include "st-nci_se.h"
|
||||
|
||||
#define DRIVER_DESC "NCI NFC driver for ST_NCI"
|
||||
|
||||
#define ST_NCI1_X_PROPRIETARY_ISO15693 0x83
|
||||
|
||||
static int st_nci_init(struct nci_dev *ndev)
|
||||
{
|
||||
struct nci_mode_set_cmd cmd;
|
||||
|
||||
cmd.cmd_type = ST_NCI_SET_NFC_MODE;
|
||||
cmd.mode = 1;
|
||||
|
||||
return nci_prop_cmd(ndev, ST_NCI_CORE_PROP,
|
||||
sizeof(struct nci_mode_set_cmd), (__u8 *)&cmd);
|
||||
}
|
||||
|
||||
static int st_nci_open(struct nci_dev *ndev)
|
||||
{
|
||||
struct st_nci_info *info = nci_get_drvdata(ndev);
|
||||
int r;
|
||||
|
||||
if (test_and_set_bit(ST_NCI_RUNNING, &info->flags))
|
||||
return 0;
|
||||
|
||||
r = ndlc_open(info->ndlc);
|
||||
if (r)
|
||||
clear_bit(ST_NCI_RUNNING, &info->flags);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int st_nci_close(struct nci_dev *ndev)
|
||||
{
|
||||
struct st_nci_info *info = nci_get_drvdata(ndev);
|
||||
|
||||
if (!test_bit(ST_NCI_RUNNING, &info->flags))
|
||||
return 0;
|
||||
|
||||
ndlc_close(info->ndlc);
|
||||
|
||||
clear_bit(ST_NCI_RUNNING, &info->flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
|
||||
{
|
||||
struct st_nci_info *info = nci_get_drvdata(ndev);
|
||||
|
||||
skb->dev = (void *)ndev;
|
||||
|
||||
if (!test_bit(ST_NCI_RUNNING, &info->flags))
|
||||
return -EBUSY;
|
||||
|
||||
return ndlc_send(info->ndlc, skb);
|
||||
}
|
||||
|
||||
static __u32 st_nci_get_rfprotocol(struct nci_dev *ndev,
|
||||
__u8 rf_protocol)
|
||||
{
|
||||
return rf_protocol == ST_NCI1_X_PROPRIETARY_ISO15693 ?
|
||||
NFC_PROTO_ISO15693_MASK : 0;
|
||||
}
|
||||
|
||||
static int st_nci_prop_rsp_packet(struct nci_dev *ndev,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
__u8 status = skb->data[0];
|
||||
|
||||
nci_req_complete(ndev, status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct nci_prop_ops st_nci_prop_ops[] = {
|
||||
{
|
||||
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||
ST_NCI_CORE_PROP),
|
||||
.rsp = st_nci_prop_rsp_packet,
|
||||
},
|
||||
};
|
||||
|
||||
static struct nci_ops st_nci_ops = {
|
||||
.init = st_nci_init,
|
||||
.open = st_nci_open,
|
||||
.close = st_nci_close,
|
||||
.send = st_nci_send,
|
||||
.get_rfprotocol = st_nci_get_rfprotocol,
|
||||
.discover_se = st_nci_discover_se,
|
||||
.enable_se = st_nci_enable_se,
|
||||
.disable_se = st_nci_disable_se,
|
||||
.se_io = st_nci_se_io,
|
||||
.hci_load_session = st_nci_hci_load_session,
|
||||
.hci_event_received = st_nci_hci_event_received,
|
||||
.hci_cmd_received = st_nci_hci_cmd_received,
|
||||
.prop_ops = st_nci_prop_ops,
|
||||
.n_prop_ops = ARRAY_SIZE(st_nci_prop_ops),
|
||||
};
|
||||
|
||||
int st_nci_probe(struct llt_ndlc *ndlc, int phy_headroom,
|
||||
int phy_tailroom)
|
||||
{
|
||||
struct st_nci_info *info;
|
||||
int r;
|
||||
u32 protocols;
|
||||
|
||||
info = devm_kzalloc(ndlc->dev,
|
||||
sizeof(struct st_nci_info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
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;
|
||||
|
||||
ndlc->ndev = nci_allocate_device(&st_nci_ops, protocols,
|
||||
phy_headroom, phy_tailroom);
|
||||
if (!ndlc->ndev) {
|
||||
pr_err("Cannot allocate nfc ndev\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
info->ndlc = ndlc;
|
||||
|
||||
nci_set_drvdata(ndlc->ndev, info);
|
||||
|
||||
r = nci_register_device(ndlc->ndev);
|
||||
if (r) {
|
||||
pr_err("Cannot register nfc device to nci core\n");
|
||||
nci_free_device(ndlc->ndev);
|
||||
return r;
|
||||
}
|
||||
|
||||
return st_nci_se_init(ndlc->ndev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(st_nci_probe);
|
||||
|
||||
void st_nci_remove(struct nci_dev *ndev)
|
||||
{
|
||||
struct st_nci_info *info = nci_get_drvdata(ndev);
|
||||
|
||||
ndlc_close(info->ndlc);
|
||||
|
||||
nci_unregister_device(ndev);
|
||||
nci_free_device(ndev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(st_nci_remove);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* I2C Link Layer for ST21NFCB NCI based Driver
|
||||
* Copyright (C) 2014 STMicroelectronics SAS. All rights reserved.
|
||||
* I2C Link Layer for ST NCI NFC controller familly based Driver
|
||||
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
@ -25,7 +25,7 @@
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/nfc.h>
|
||||
#include <linux/platform_data/st21nfcb.h>
|
||||
#include <linux/platform_data/st_nci.h>
|
||||
|
||||
#include "ndlc.h"
|
||||
|
||||
@ -35,25 +35,23 @@
|
||||
#define ST21NFCB_FRAME_HEADROOM 1
|
||||
#define ST21NFCB_FRAME_TAILROOM 0
|
||||
|
||||
#define ST21NFCB_NCI_I2C_MIN_SIZE 4 /* PCB(1) + NCI Packet header(3) */
|
||||
#define ST21NFCB_NCI_I2C_MAX_SIZE 250 /* req 4.2.1 */
|
||||
#define ST_NCI_I2C_MIN_SIZE 4 /* PCB(1) + NCI Packet header(3) */
|
||||
#define ST_NCI_I2C_MAX_SIZE 250 /* req 4.2.1 */
|
||||
|
||||
#define ST21NFCB_NCI_I2C_DRIVER_NAME "st21nfcb_nci_i2c"
|
||||
#define ST_NCI_I2C_DRIVER_NAME "st_nci_i2c"
|
||||
|
||||
static struct i2c_device_id st21nfcb_nci_i2c_id_table[] = {
|
||||
{ST21NFCB_NCI_DRIVER_NAME, 0},
|
||||
static struct i2c_device_id st_nci_i2c_id_table[] = {
|
||||
{ST_NCI_DRIVER_NAME, 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, st21nfcb_nci_i2c_id_table);
|
||||
MODULE_DEVICE_TABLE(i2c, st_nci_i2c_id_table);
|
||||
|
||||
struct st21nfcb_i2c_phy {
|
||||
struct st_nci_i2c_phy {
|
||||
struct i2c_client *i2c_dev;
|
||||
struct llt_ndlc *ndlc;
|
||||
|
||||
unsigned int gpio_reset;
|
||||
unsigned int irq_polarity;
|
||||
|
||||
int powered;
|
||||
};
|
||||
|
||||
#define I2C_DUMP_SKB(info, skb) \
|
||||
@ -63,33 +61,26 @@ do { \
|
||||
16, 1, (skb)->data, (skb)->len, 0); \
|
||||
} while (0)
|
||||
|
||||
static int st21nfcb_nci_i2c_enable(void *phy_id)
|
||||
static int st_nci_i2c_enable(void *phy_id)
|
||||
{
|
||||
struct st21nfcb_i2c_phy *phy = phy_id;
|
||||
struct st_nci_i2c_phy *phy = phy_id;
|
||||
|
||||
gpio_set_value(phy->gpio_reset, 0);
|
||||
usleep_range(10000, 15000);
|
||||
gpio_set_value(phy->gpio_reset, 1);
|
||||
phy->powered = 1;
|
||||
usleep_range(80000, 85000);
|
||||
|
||||
if (phy->ndlc->powered == 0)
|
||||
enable_irq(phy->i2c_dev->irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void st21nfcb_nci_i2c_disable(void *phy_id)
|
||||
static void st_nci_i2c_disable(void *phy_id)
|
||||
{
|
||||
struct st21nfcb_i2c_phy *phy = phy_id;
|
||||
struct st_nci_i2c_phy *phy = phy_id;
|
||||
|
||||
phy->powered = 0;
|
||||
/* reset chip in order to flush clf */
|
||||
gpio_set_value(phy->gpio_reset, 0);
|
||||
usleep_range(10000, 15000);
|
||||
gpio_set_value(phy->gpio_reset, 1);
|
||||
}
|
||||
|
||||
static void st21nfcb_nci_remove_header(struct sk_buff *skb)
|
||||
{
|
||||
skb_pull(skb, ST21NFCB_FRAME_HEADROOM);
|
||||
disable_irq_nosync(phy->i2c_dev->irq);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -97,13 +88,13 @@ static void st21nfcb_nci_remove_header(struct sk_buff *skb)
|
||||
* It must return either zero for success, or <0 for error.
|
||||
* In addition, it must not alter the skb
|
||||
*/
|
||||
static int st21nfcb_nci_i2c_write(void *phy_id, struct sk_buff *skb)
|
||||
static int st_nci_i2c_write(void *phy_id, struct sk_buff *skb)
|
||||
{
|
||||
int r = -1;
|
||||
struct st21nfcb_i2c_phy *phy = phy_id;
|
||||
struct st_nci_i2c_phy *phy = phy_id;
|
||||
struct i2c_client *client = phy->i2c_dev;
|
||||
|
||||
I2C_DUMP_SKB("st21nfcb_nci_i2c_write", skb);
|
||||
I2C_DUMP_SKB("st_nci_i2c_write", skb);
|
||||
|
||||
if (phy->ndlc->hard_fault != 0)
|
||||
return phy->ndlc->hard_fault;
|
||||
@ -121,8 +112,6 @@ static int st21nfcb_nci_i2c_write(void *phy_id, struct sk_buff *skb)
|
||||
r = 0;
|
||||
}
|
||||
|
||||
st21nfcb_nci_remove_header(skb);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@ -135,40 +124,40 @@ static int st21nfcb_nci_i2c_write(void *phy_id, struct sk_buff *skb)
|
||||
* at end of read)
|
||||
* -EREMOTEIO : i2c read error (fatal)
|
||||
* -EBADMSG : frame was incorrect and discarded
|
||||
* (value returned from st21nfcb_nci_i2c_repack)
|
||||
* (value returned from st_nci_i2c_repack)
|
||||
* -EIO : if no ST21NFCB_SOF_EOF is found after reaching
|
||||
* the read length end sequence
|
||||
*/
|
||||
static int st21nfcb_nci_i2c_read(struct st21nfcb_i2c_phy *phy,
|
||||
static int st_nci_i2c_read(struct st_nci_i2c_phy *phy,
|
||||
struct sk_buff **skb)
|
||||
{
|
||||
int r;
|
||||
u8 len;
|
||||
u8 buf[ST21NFCB_NCI_I2C_MAX_SIZE];
|
||||
u8 buf[ST_NCI_I2C_MAX_SIZE];
|
||||
struct i2c_client *client = phy->i2c_dev;
|
||||
|
||||
r = i2c_master_recv(client, buf, ST21NFCB_NCI_I2C_MIN_SIZE);
|
||||
r = i2c_master_recv(client, buf, ST_NCI_I2C_MIN_SIZE);
|
||||
if (r < 0) { /* Retry, chip was in standby */
|
||||
usleep_range(1000, 4000);
|
||||
r = i2c_master_recv(client, buf, ST21NFCB_NCI_I2C_MIN_SIZE);
|
||||
r = i2c_master_recv(client, buf, ST_NCI_I2C_MIN_SIZE);
|
||||
}
|
||||
|
||||
if (r != ST21NFCB_NCI_I2C_MIN_SIZE)
|
||||
if (r != ST_NCI_I2C_MIN_SIZE)
|
||||
return -EREMOTEIO;
|
||||
|
||||
len = be16_to_cpu(*(__be16 *) (buf + 2));
|
||||
if (len > ST21NFCB_NCI_I2C_MAX_SIZE) {
|
||||
if (len > ST_NCI_I2C_MAX_SIZE) {
|
||||
nfc_err(&client->dev, "invalid frame len\n");
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
*skb = alloc_skb(ST21NFCB_NCI_I2C_MIN_SIZE + len, GFP_KERNEL);
|
||||
*skb = alloc_skb(ST_NCI_I2C_MIN_SIZE + len, GFP_KERNEL);
|
||||
if (*skb == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
skb_reserve(*skb, ST21NFCB_NCI_I2C_MIN_SIZE);
|
||||
skb_put(*skb, ST21NFCB_NCI_I2C_MIN_SIZE);
|
||||
memcpy((*skb)->data, buf, ST21NFCB_NCI_I2C_MIN_SIZE);
|
||||
skb_reserve(*skb, ST_NCI_I2C_MIN_SIZE);
|
||||
skb_put(*skb, ST_NCI_I2C_MIN_SIZE);
|
||||
memcpy((*skb)->data, buf, ST_NCI_I2C_MIN_SIZE);
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
@ -180,7 +169,7 @@ static int st21nfcb_nci_i2c_read(struct st21nfcb_i2c_phy *phy,
|
||||
}
|
||||
|
||||
skb_put(*skb, len);
|
||||
memcpy((*skb)->data + ST21NFCB_NCI_I2C_MIN_SIZE, buf, len);
|
||||
memcpy((*skb)->data + ST_NCI_I2C_MIN_SIZE, buf, len);
|
||||
|
||||
I2C_DUMP_SKB("i2c frame read", *skb);
|
||||
|
||||
@ -192,9 +181,9 @@ static int st21nfcb_nci_i2c_read(struct st21nfcb_i2c_phy *phy,
|
||||
*
|
||||
* On ST21NFCB, IRQ goes in idle state when read starts.
|
||||
*/
|
||||
static irqreturn_t st21nfcb_nci_irq_thread_fn(int irq, void *phy_id)
|
||||
static irqreturn_t st_nci_irq_thread_fn(int irq, void *phy_id)
|
||||
{
|
||||
struct st21nfcb_i2c_phy *phy = phy_id;
|
||||
struct st_nci_i2c_phy *phy = phy_id;
|
||||
struct i2c_client *client;
|
||||
struct sk_buff *skb = NULL;
|
||||
int r;
|
||||
@ -210,12 +199,12 @@ static irqreturn_t st21nfcb_nci_irq_thread_fn(int irq, void *phy_id)
|
||||
if (phy->ndlc->hard_fault)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
if (!phy->powered) {
|
||||
st21nfcb_nci_i2c_disable(phy);
|
||||
if (!phy->ndlc->powered) {
|
||||
st_nci_i2c_disable(phy);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
r = st21nfcb_nci_i2c_read(phy, &skb);
|
||||
r = st_nci_i2c_read(phy, &skb);
|
||||
if (r == -EREMOTEIO || r == -ENOMEM || r == -EBADMSG)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
@ -225,15 +214,15 @@ static irqreturn_t st21nfcb_nci_irq_thread_fn(int irq, void *phy_id)
|
||||
}
|
||||
|
||||
static struct nfc_phy_ops i2c_phy_ops = {
|
||||
.write = st21nfcb_nci_i2c_write,
|
||||
.enable = st21nfcb_nci_i2c_enable,
|
||||
.disable = st21nfcb_nci_i2c_disable,
|
||||
.write = st_nci_i2c_write,
|
||||
.enable = st_nci_i2c_enable,
|
||||
.disable = st_nci_i2c_disable,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static int st21nfcb_nci_i2c_of_request_resources(struct i2c_client *client)
|
||||
static int st_nci_i2c_of_request_resources(struct i2c_client *client)
|
||||
{
|
||||
struct st21nfcb_i2c_phy *phy = i2c_get_clientdata(client);
|
||||
struct st_nci_i2c_phy *phy = i2c_get_clientdata(client);
|
||||
struct device_node *pp;
|
||||
int gpio;
|
||||
int r;
|
||||
@ -264,16 +253,16 @@ static int st21nfcb_nci_i2c_of_request_resources(struct i2c_client *client)
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int st21nfcb_nci_i2c_of_request_resources(struct i2c_client *client)
|
||||
static int st_nci_i2c_of_request_resources(struct i2c_client *client)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int st21nfcb_nci_i2c_request_resources(struct i2c_client *client)
|
||||
static int st_nci_i2c_request_resources(struct i2c_client *client)
|
||||
{
|
||||
struct st21nfcb_nfc_platform_data *pdata;
|
||||
struct st21nfcb_i2c_phy *phy = i2c_get_clientdata(client);
|
||||
struct st_nci_nfc_platform_data *pdata;
|
||||
struct st_nci_i2c_phy *phy = i2c_get_clientdata(client);
|
||||
int r;
|
||||
|
||||
pdata = client->dev.platform_data;
|
||||
@ -296,11 +285,11 @@ static int st21nfcb_nci_i2c_request_resources(struct i2c_client *client)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st21nfcb_nci_i2c_probe(struct i2c_client *client,
|
||||
static int st_nci_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct st21nfcb_i2c_phy *phy;
|
||||
struct st21nfcb_nfc_platform_data *pdata;
|
||||
struct st_nci_i2c_phy *phy;
|
||||
struct st_nci_nfc_platform_data *pdata;
|
||||
int r;
|
||||
|
||||
dev_dbg(&client->dev, "%s\n", __func__);
|
||||
@ -311,7 +300,7 @@ static int st21nfcb_nci_i2c_probe(struct i2c_client *client,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
phy = devm_kzalloc(&client->dev, sizeof(struct st21nfcb_i2c_phy),
|
||||
phy = devm_kzalloc(&client->dev, sizeof(struct st_nci_i2c_phy),
|
||||
GFP_KERNEL);
|
||||
if (!phy)
|
||||
return -ENOMEM;
|
||||
@ -322,13 +311,13 @@ static int st21nfcb_nci_i2c_probe(struct i2c_client *client,
|
||||
|
||||
pdata = client->dev.platform_data;
|
||||
if (!pdata && client->dev.of_node) {
|
||||
r = st21nfcb_nci_i2c_of_request_resources(client);
|
||||
r = st_nci_i2c_of_request_resources(client);
|
||||
if (r) {
|
||||
nfc_err(&client->dev, "No platform data\n");
|
||||
return r;
|
||||
}
|
||||
} else if (pdata) {
|
||||
r = st21nfcb_nci_i2c_request_resources(client);
|
||||
r = st_nci_i2c_request_resources(client);
|
||||
if (r) {
|
||||
nfc_err(&client->dev,
|
||||
"Cannot get platform resources\n");
|
||||
@ -349,50 +338,48 @@ static int st21nfcb_nci_i2c_probe(struct i2c_client *client,
|
||||
}
|
||||
|
||||
r = devm_request_threaded_irq(&client->dev, client->irq, NULL,
|
||||
st21nfcb_nci_irq_thread_fn,
|
||||
st_nci_irq_thread_fn,
|
||||
phy->irq_polarity | IRQF_ONESHOT,
|
||||
ST21NFCB_NCI_DRIVER_NAME, phy);
|
||||
ST_NCI_DRIVER_NAME, phy);
|
||||
if (r < 0)
|
||||
nfc_err(&client->dev, "Unable to register IRQ handler\n");
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int st21nfcb_nci_i2c_remove(struct i2c_client *client)
|
||||
static int st_nci_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct st21nfcb_i2c_phy *phy = i2c_get_clientdata(client);
|
||||
struct st_nci_i2c_phy *phy = i2c_get_clientdata(client);
|
||||
|
||||
dev_dbg(&client->dev, "%s\n", __func__);
|
||||
|
||||
ndlc_remove(phy->ndlc);
|
||||
|
||||
if (phy->powered)
|
||||
st21nfcb_nci_i2c_disable(phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id of_st21nfcb_i2c_match[] = {
|
||||
static const struct of_device_id of_st_nci_i2c_match[] = {
|
||||
{ .compatible = "st,st21nfcb-i2c", },
|
||||
{ .compatible = "st,st21nfcb_i2c", },
|
||||
{ .compatible = "st,st21nfcc-i2c", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_st21nfcb_i2c_match);
|
||||
MODULE_DEVICE_TABLE(of, of_st_nci_i2c_match);
|
||||
#endif
|
||||
|
||||
static struct i2c_driver st21nfcb_nci_i2c_driver = {
|
||||
static struct i2c_driver st_nci_i2c_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = ST21NFCB_NCI_I2C_DRIVER_NAME,
|
||||
.of_match_table = of_match_ptr(of_st21nfcb_i2c_match),
|
||||
.name = ST_NCI_I2C_DRIVER_NAME,
|
||||
.of_match_table = of_match_ptr(of_st_nci_i2c_match),
|
||||
},
|
||||
.probe = st21nfcb_nci_i2c_probe,
|
||||
.id_table = st21nfcb_nci_i2c_id_table,
|
||||
.remove = st21nfcb_nci_i2c_remove,
|
||||
.probe = st_nci_i2c_probe,
|
||||
.id_table = st_nci_i2c_id_table,
|
||||
.remove = st_nci_i2c_remove,
|
||||
};
|
||||
|
||||
module_i2c_driver(st21nfcb_nci_i2c_driver);
|
||||
module_i2c_driver(st_nci_i2c_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Low Level Transport (NDLC) Driver for STMicroelectronics NFC Chip
|
||||
*
|
||||
* Copyright (C) 2014 STMicroelectronics SAS. All rights reserved.
|
||||
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
@ -20,7 +20,7 @@
|
||||
#include <net/nfc/nci_core.h>
|
||||
|
||||
#include "ndlc.h"
|
||||
#include "st21nfcb.h"
|
||||
#include "st-nci.h"
|
||||
|
||||
#define NDLC_TIMER_T1 100
|
||||
#define NDLC_TIMER_T1_WAIT 400
|
||||
@ -59,13 +59,25 @@ int ndlc_open(struct llt_ndlc *ndlc)
|
||||
{
|
||||
/* toggle reset pin */
|
||||
ndlc->ops->enable(ndlc->phy_id);
|
||||
ndlc->powered = 1;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(ndlc_open);
|
||||
|
||||
void ndlc_close(struct llt_ndlc *ndlc)
|
||||
{
|
||||
struct nci_mode_set_cmd cmd;
|
||||
|
||||
cmd.cmd_type = ST_NCI_SET_NFC_MODE;
|
||||
cmd.mode = 0;
|
||||
|
||||
/* toggle reset pin */
|
||||
ndlc->ops->enable(ndlc->phy_id);
|
||||
|
||||
nci_prop_cmd(ndlc->ndev, ST_NCI_CORE_PROP,
|
||||
sizeof(struct nci_mode_set_cmd), (__u8 *)&cmd);
|
||||
|
||||
ndlc->powered = 0;
|
||||
ndlc->ops->disable(ndlc->phy_id);
|
||||
}
|
||||
EXPORT_SYMBOL(ndlc_close);
|
||||
@ -262,6 +274,7 @@ int ndlc_probe(void *phy_id, struct nfc_phy_ops *phy_ops, struct device *dev,
|
||||
ndlc->ops = phy_ops;
|
||||
ndlc->phy_id = phy_id;
|
||||
ndlc->dev = dev;
|
||||
ndlc->powered = 0;
|
||||
|
||||
*ndlc_id = ndlc;
|
||||
|
||||
@ -280,12 +293,14 @@ int ndlc_probe(void *phy_id, struct nfc_phy_ops *phy_ops, struct device *dev,
|
||||
|
||||
INIT_WORK(&ndlc->sm_work, llt_ndlc_sm_work);
|
||||
|
||||
return st21nfcb_nci_probe(ndlc, phy_headroom, phy_tailroom);
|
||||
return st_nci_probe(ndlc, phy_headroom, phy_tailroom);
|
||||
}
|
||||
EXPORT_SYMBOL(ndlc_probe);
|
||||
|
||||
void ndlc_remove(struct llt_ndlc *ndlc)
|
||||
{
|
||||
st_nci_remove(ndlc->ndev);
|
||||
|
||||
/* cancel timers */
|
||||
del_timer_sync(&ndlc->t1_timer);
|
||||
del_timer_sync(&ndlc->t2_timer);
|
||||
@ -294,7 +309,5 @@ void ndlc_remove(struct llt_ndlc *ndlc)
|
||||
|
||||
skb_queue_purge(&ndlc->rcv_q);
|
||||
skb_queue_purge(&ndlc->send_q);
|
||||
|
||||
st21nfcb_nci_remove(ndlc->ndev);
|
||||
}
|
||||
EXPORT_SYMBOL(ndlc_remove);
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* NCI based Driver for STMicroelectronics NFC Chip
|
||||
*
|
||||
* Copyright (C) 2014 STMicroelectronics SAS. All rights reserved.
|
||||
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
@ -43,10 +43,11 @@ struct llt_ndlc {
|
||||
struct device *dev;
|
||||
|
||||
/*
|
||||
* < 0 if hardware error occured
|
||||
* < 0 if hardware error occurred
|
||||
* and prevents normal operation.
|
||||
*/
|
||||
int hard_fault;
|
||||
int powered;
|
||||
};
|
||||
|
||||
int ndlc_open(struct llt_ndlc *ndlc);
|
@ -16,23 +16,35 @@
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __LOCAL_ST21NFCB_H_
|
||||
#define __LOCAL_ST21NFCB_H_
|
||||
#ifndef __LOCAL_ST_NCI_H_
|
||||
#define __LOCAL_ST_NCI_H_
|
||||
|
||||
#include "st21nfcb_se.h"
|
||||
#include "st-nci_se.h"
|
||||
#include "ndlc.h"
|
||||
|
||||
/* Define private flags: */
|
||||
#define ST21NFCB_NCI_RUNNING 1
|
||||
#define ST_NCI_RUNNING 1
|
||||
|
||||
struct st21nfcb_nci_info {
|
||||
#define ST_NCI_CORE_PROP 0x01
|
||||
#define ST_NCI_SET_NFC_MODE 0x02
|
||||
|
||||
struct nci_mode_set_cmd {
|
||||
u8 cmd_type;
|
||||
u8 mode;
|
||||
} __packed;
|
||||
|
||||
struct nci_mode_set_rsp {
|
||||
u8 status;
|
||||
} __packed;
|
||||
|
||||
struct st_nci_info {
|
||||
struct llt_ndlc *ndlc;
|
||||
unsigned long flags;
|
||||
struct st21nfcb_se_info se_info;
|
||||
struct st_nci_se_info se_info;
|
||||
};
|
||||
|
||||
void st21nfcb_nci_remove(struct nci_dev *ndev);
|
||||
int st21nfcb_nci_probe(struct llt_ndlc *ndlc, int phy_headroom,
|
||||
void st_nci_remove(struct nci_dev *ndev);
|
||||
int st_nci_probe(struct llt_ndlc *ndlc, int phy_headroom,
|
||||
int phy_tailroom);
|
||||
|
||||
#endif /* __LOCAL_ST21NFCB_H_ */
|
||||
#endif /* __LOCAL_ST_NCI_H_ */
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* NCI based Driver for STMicroelectronics NFC Chip
|
||||
* Secure Element driver for STMicroelectronics NFC NCI chip
|
||||
*
|
||||
* Copyright (C) 2014 STMicroelectronics SAS. All rights reserved.
|
||||
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
@ -22,10 +22,10 @@
|
||||
#include <net/nfc/nci.h>
|
||||
#include <net/nfc/nci_core.h>
|
||||
|
||||
#include "st21nfcb.h"
|
||||
#include "st21nfcb_se.h"
|
||||
#include "st-nci.h"
|
||||
#include "st-nci_se.h"
|
||||
|
||||
struct st21nfcb_pipe_info {
|
||||
struct st_nci_pipe_info {
|
||||
u8 pipe_state;
|
||||
u8 src_host_id;
|
||||
u8 src_gate_id;
|
||||
@ -34,166 +34,166 @@ struct st21nfcb_pipe_info {
|
||||
} __packed;
|
||||
|
||||
/* Hosts */
|
||||
#define ST21NFCB_HOST_CONTROLLER_ID 0x00
|
||||
#define ST21NFCB_TERMINAL_HOST_ID 0x01
|
||||
#define ST21NFCB_UICC_HOST_ID 0x02
|
||||
#define ST21NFCB_ESE_HOST_ID 0xc0
|
||||
#define ST_NCI_HOST_CONTROLLER_ID 0x00
|
||||
#define ST_NCI_TERMINAL_HOST_ID 0x01
|
||||
#define ST_NCI_UICC_HOST_ID 0x02
|
||||
#define ST_NCI_ESE_HOST_ID 0xc0
|
||||
|
||||
/* Gates */
|
||||
#define ST21NFCB_DEVICE_MGNT_GATE 0x01
|
||||
#define ST21NFCB_APDU_READER_GATE 0xf0
|
||||
#define ST21NFCB_CONNECTIVITY_GATE 0x41
|
||||
#define ST_NCI_DEVICE_MGNT_GATE 0x01
|
||||
#define ST_NCI_APDU_READER_GATE 0xf0
|
||||
#define ST_NCI_CONNECTIVITY_GATE 0x41
|
||||
|
||||
/* Pipes */
|
||||
#define ST21NFCB_DEVICE_MGNT_PIPE 0x02
|
||||
#define ST_NCI_DEVICE_MGNT_PIPE 0x02
|
||||
|
||||
/* Connectivity pipe only */
|
||||
#define ST21NFCB_SE_COUNT_PIPE_UICC 0x01
|
||||
#define ST_NCI_SE_COUNT_PIPE_UICC 0x01
|
||||
/* Connectivity + APDU Reader pipe */
|
||||
#define ST21NFCB_SE_COUNT_PIPE_EMBEDDED 0x02
|
||||
#define ST_NCI_SE_COUNT_PIPE_EMBEDDED 0x02
|
||||
|
||||
#define ST21NFCB_SE_TO_HOT_PLUG 1000 /* msecs */
|
||||
#define ST21NFCB_SE_TO_PIPES 2000
|
||||
#define ST_NCI_SE_TO_HOT_PLUG 1000 /* msecs */
|
||||
#define ST_NCI_SE_TO_PIPES 2000
|
||||
|
||||
#define ST21NFCB_EVT_HOT_PLUG_IS_INHIBITED(x) (x->data[0] & 0x80)
|
||||
#define ST_NCI_EVT_HOT_PLUG_IS_INHIBITED(x) (x->data[0] & 0x80)
|
||||
|
||||
#define NCI_HCI_APDU_PARAM_ATR 0x01
|
||||
#define NCI_HCI_ADMIN_PARAM_SESSION_IDENTITY 0x01
|
||||
#define NCI_HCI_ADMIN_PARAM_WHITELIST 0x03
|
||||
#define NCI_HCI_ADMIN_PARAM_HOST_LIST 0x04
|
||||
|
||||
#define ST21NFCB_EVT_SE_HARD_RESET 0x20
|
||||
#define ST21NFCB_EVT_TRANSMIT_DATA 0x10
|
||||
#define ST21NFCB_EVT_WTX_REQUEST 0x11
|
||||
#define ST21NFCB_EVT_SE_SOFT_RESET 0x11
|
||||
#define ST21NFCB_EVT_SE_END_OF_APDU_TRANSFER 0x21
|
||||
#define ST21NFCB_EVT_HOT_PLUG 0x03
|
||||
#define ST_NCI_EVT_SE_HARD_RESET 0x20
|
||||
#define ST_NCI_EVT_TRANSMIT_DATA 0x10
|
||||
#define ST_NCI_EVT_WTX_REQUEST 0x11
|
||||
#define ST_NCI_EVT_SE_SOFT_RESET 0x11
|
||||
#define ST_NCI_EVT_SE_END_OF_APDU_TRANSFER 0x21
|
||||
#define ST_NCI_EVT_HOT_PLUG 0x03
|
||||
|
||||
#define ST21NFCB_SE_MODE_OFF 0x00
|
||||
#define ST21NFCB_SE_MODE_ON 0x01
|
||||
#define ST_NCI_SE_MODE_OFF 0x00
|
||||
#define ST_NCI_SE_MODE_ON 0x01
|
||||
|
||||
#define ST21NFCB_EVT_CONNECTIVITY 0x10
|
||||
#define ST21NFCB_EVT_TRANSACTION 0x12
|
||||
#define ST_NCI_EVT_CONNECTIVITY 0x10
|
||||
#define ST_NCI_EVT_TRANSACTION 0x12
|
||||
|
||||
#define ST21NFCB_DM_GETINFO 0x13
|
||||
#define ST21NFCB_DM_GETINFO_PIPE_LIST 0x02
|
||||
#define ST21NFCB_DM_GETINFO_PIPE_INFO 0x01
|
||||
#define ST21NFCB_DM_PIPE_CREATED 0x02
|
||||
#define ST21NFCB_DM_PIPE_OPEN 0x04
|
||||
#define ST21NFCB_DM_RF_ACTIVE 0x80
|
||||
#define ST21NFCB_DM_DISCONNECT 0x30
|
||||
#define ST_NCI_DM_GETINFO 0x13
|
||||
#define ST_NCI_DM_GETINFO_PIPE_LIST 0x02
|
||||
#define ST_NCI_DM_GETINFO_PIPE_INFO 0x01
|
||||
#define ST_NCI_DM_PIPE_CREATED 0x02
|
||||
#define ST_NCI_DM_PIPE_OPEN 0x04
|
||||
#define ST_NCI_DM_RF_ACTIVE 0x80
|
||||
#define ST_NCI_DM_DISCONNECT 0x30
|
||||
|
||||
#define ST21NFCB_DM_IS_PIPE_OPEN(p) \
|
||||
((p & 0x0f) == (ST21NFCB_DM_PIPE_CREATED | ST21NFCB_DM_PIPE_OPEN))
|
||||
#define ST_NCI_DM_IS_PIPE_OPEN(p) \
|
||||
((p & 0x0f) == (ST_NCI_DM_PIPE_CREATED | ST_NCI_DM_PIPE_OPEN))
|
||||
|
||||
#define ST21NFCB_ATR_DEFAULT_BWI 0x04
|
||||
#define ST_NCI_ATR_DEFAULT_BWI 0x04
|
||||
|
||||
/*
|
||||
* WT = 2^BWI/10[s], convert into msecs and add a secure
|
||||
* room by increasing by 2 this timeout
|
||||
*/
|
||||
#define ST21NFCB_BWI_TO_TIMEOUT(x) ((1 << x) * 200)
|
||||
#define ST21NFCB_ATR_GET_Y_FROM_TD(x) (x >> 4)
|
||||
#define ST_NCI_BWI_TO_TIMEOUT(x) ((1 << x) * 200)
|
||||
#define ST_NCI_ATR_GET_Y_FROM_TD(x) (x >> 4)
|
||||
|
||||
/* If TA is present bit 0 is set */
|
||||
#define ST21NFCB_ATR_TA_PRESENT(x) (x & 0x01)
|
||||
#define ST_NCI_ATR_TA_PRESENT(x) (x & 0x01)
|
||||
/* If TB is present bit 1 is set */
|
||||
#define ST21NFCB_ATR_TB_PRESENT(x) (x & 0x02)
|
||||
#define ST_NCI_ATR_TB_PRESENT(x) (x & 0x02)
|
||||
|
||||
#define ST21NFCB_NUM_DEVICES 256
|
||||
#define ST_NCI_NUM_DEVICES 256
|
||||
|
||||
static DECLARE_BITMAP(dev_mask, ST21NFCB_NUM_DEVICES);
|
||||
static DECLARE_BITMAP(dev_mask, ST_NCI_NUM_DEVICES);
|
||||
|
||||
/* Here are the mandatory pipe for st21nfcb */
|
||||
static struct nci_hci_gate st21nfcb_gates[] = {
|
||||
/* Here are the mandatory pipe for st_nci */
|
||||
static struct nci_hci_gate st_nci_gates[] = {
|
||||
{NCI_HCI_ADMIN_GATE, NCI_HCI_ADMIN_PIPE,
|
||||
ST21NFCB_HOST_CONTROLLER_ID},
|
||||
ST_NCI_HOST_CONTROLLER_ID},
|
||||
{NCI_HCI_LINK_MGMT_GATE, NCI_HCI_LINK_MGMT_PIPE,
|
||||
ST21NFCB_HOST_CONTROLLER_ID},
|
||||
{ST21NFCB_DEVICE_MGNT_GATE, ST21NFCB_DEVICE_MGNT_PIPE,
|
||||
ST21NFCB_HOST_CONTROLLER_ID},
|
||||
ST_NCI_HOST_CONTROLLER_ID},
|
||||
{ST_NCI_DEVICE_MGNT_GATE, ST_NCI_DEVICE_MGNT_PIPE,
|
||||
ST_NCI_HOST_CONTROLLER_ID},
|
||||
|
||||
/* Secure element pipes are created by secure element host */
|
||||
{ST21NFCB_CONNECTIVITY_GATE, NCI_HCI_DO_NOT_OPEN_PIPE,
|
||||
ST21NFCB_HOST_CONTROLLER_ID},
|
||||
{ST21NFCB_APDU_READER_GATE, NCI_HCI_DO_NOT_OPEN_PIPE,
|
||||
ST21NFCB_HOST_CONTROLLER_ID},
|
||||
{ST_NCI_CONNECTIVITY_GATE, NCI_HCI_DO_NOT_OPEN_PIPE,
|
||||
ST_NCI_HOST_CONTROLLER_ID},
|
||||
{ST_NCI_APDU_READER_GATE, NCI_HCI_DO_NOT_OPEN_PIPE,
|
||||
ST_NCI_HOST_CONTROLLER_ID},
|
||||
};
|
||||
|
||||
static u8 st21nfcb_se_get_bwi(struct nci_dev *ndev)
|
||||
static u8 st_nci_se_get_bwi(struct nci_dev *ndev)
|
||||
{
|
||||
int i;
|
||||
u8 td;
|
||||
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
|
||||
struct st_nci_info *info = nci_get_drvdata(ndev);
|
||||
|
||||
/* Bits 8 to 5 of the first TB for T=1 encode BWI from zero to nine */
|
||||
for (i = 1; i < ST21NFCB_ESE_MAX_LENGTH; i++) {
|
||||
td = ST21NFCB_ATR_GET_Y_FROM_TD(info->se_info.atr[i]);
|
||||
if (ST21NFCB_ATR_TA_PRESENT(td))
|
||||
for (i = 1; i < ST_NCI_ESE_MAX_LENGTH; i++) {
|
||||
td = ST_NCI_ATR_GET_Y_FROM_TD(info->se_info.atr[i]);
|
||||
if (ST_NCI_ATR_TA_PRESENT(td))
|
||||
i++;
|
||||
if (ST21NFCB_ATR_TB_PRESENT(td)) {
|
||||
if (ST_NCI_ATR_TB_PRESENT(td)) {
|
||||
i++;
|
||||
return info->se_info.atr[i] >> 4;
|
||||
}
|
||||
}
|
||||
return ST21NFCB_ATR_DEFAULT_BWI;
|
||||
return ST_NCI_ATR_DEFAULT_BWI;
|
||||
}
|
||||
|
||||
static void st21nfcb_se_get_atr(struct nci_dev *ndev)
|
||||
static void st_nci_se_get_atr(struct nci_dev *ndev)
|
||||
{
|
||||
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
|
||||
struct st_nci_info *info = nci_get_drvdata(ndev);
|
||||
int r;
|
||||
struct sk_buff *skb;
|
||||
|
||||
r = nci_hci_get_param(ndev, ST21NFCB_APDU_READER_GATE,
|
||||
r = nci_hci_get_param(ndev, ST_NCI_APDU_READER_GATE,
|
||||
NCI_HCI_APDU_PARAM_ATR, &skb);
|
||||
if (r < 0)
|
||||
return;
|
||||
|
||||
if (skb->len <= ST21NFCB_ESE_MAX_LENGTH) {
|
||||
if (skb->len <= ST_NCI_ESE_MAX_LENGTH) {
|
||||
memcpy(info->se_info.atr, skb->data, skb->len);
|
||||
|
||||
info->se_info.wt_timeout =
|
||||
ST21NFCB_BWI_TO_TIMEOUT(st21nfcb_se_get_bwi(ndev));
|
||||
ST_NCI_BWI_TO_TIMEOUT(st_nci_se_get_bwi(ndev));
|
||||
}
|
||||
kfree_skb(skb);
|
||||
}
|
||||
|
||||
int st21nfcb_hci_load_session(struct nci_dev *ndev)
|
||||
int st_nci_hci_load_session(struct nci_dev *ndev)
|
||||
{
|
||||
int i, j, r;
|
||||
struct sk_buff *skb_pipe_list, *skb_pipe_info;
|
||||
struct st21nfcb_pipe_info *dm_pipe_info;
|
||||
u8 pipe_list[] = { ST21NFCB_DM_GETINFO_PIPE_LIST,
|
||||
ST21NFCB_TERMINAL_HOST_ID};
|
||||
u8 pipe_info[] = { ST21NFCB_DM_GETINFO_PIPE_INFO,
|
||||
ST21NFCB_TERMINAL_HOST_ID, 0};
|
||||
struct st_nci_pipe_info *dm_pipe_info;
|
||||
u8 pipe_list[] = { ST_NCI_DM_GETINFO_PIPE_LIST,
|
||||
ST_NCI_TERMINAL_HOST_ID};
|
||||
u8 pipe_info[] = { ST_NCI_DM_GETINFO_PIPE_INFO,
|
||||
ST_NCI_TERMINAL_HOST_ID, 0};
|
||||
|
||||
/* On ST21NFCB device pipes number are dynamics
|
||||
/* On ST_NCI device pipes number are dynamics
|
||||
* If pipes are already created, hci_dev_up will fail.
|
||||
* Doing a clear all pipe is a bad idea because:
|
||||
* - It does useless EEPROM cycling
|
||||
* - It might cause issue for secure elements support
|
||||
* (such as removing connectivity or APDU reader pipe)
|
||||
* A better approach on ST21NFCB is to:
|
||||
* A better approach on ST_NCI is to:
|
||||
* - get a pipe list for each host.
|
||||
* (eg: ST21NFCB_HOST_CONTROLLER_ID for now).
|
||||
* (eg: ST_NCI_HOST_CONTROLLER_ID for now).
|
||||
* (TODO Later on UICC HOST and eSE HOST)
|
||||
* - get pipe information
|
||||
* - match retrieved pipe list in st21nfcb_gates
|
||||
* ST21NFCB_DEVICE_MGNT_GATE is a proprietary gate
|
||||
* with ST21NFCB_DEVICE_MGNT_PIPE.
|
||||
* - match retrieved pipe list in st_nci_gates
|
||||
* ST_NCI_DEVICE_MGNT_GATE is a proprietary gate
|
||||
* with ST_NCI_DEVICE_MGNT_PIPE.
|
||||
* Pipe can be closed and need to be open.
|
||||
*/
|
||||
r = nci_hci_connect_gate(ndev, ST21NFCB_HOST_CONTROLLER_ID,
|
||||
ST21NFCB_DEVICE_MGNT_GATE,
|
||||
ST21NFCB_DEVICE_MGNT_PIPE);
|
||||
r = nci_hci_connect_gate(ndev, ST_NCI_HOST_CONTROLLER_ID,
|
||||
ST_NCI_DEVICE_MGNT_GATE,
|
||||
ST_NCI_DEVICE_MGNT_PIPE);
|
||||
if (r < 0)
|
||||
goto free_info;
|
||||
|
||||
/* Get pipe list */
|
||||
r = nci_hci_send_cmd(ndev, ST21NFCB_DEVICE_MGNT_GATE,
|
||||
ST21NFCB_DM_GETINFO, pipe_list, sizeof(pipe_list),
|
||||
r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
|
||||
ST_NCI_DM_GETINFO, pipe_list, sizeof(pipe_list),
|
||||
&skb_pipe_list);
|
||||
if (r < 0)
|
||||
goto free_info;
|
||||
@ -201,8 +201,8 @@ int st21nfcb_hci_load_session(struct nci_dev *ndev)
|
||||
/* Complete the existing gate_pipe table */
|
||||
for (i = 0; i < skb_pipe_list->len; i++) {
|
||||
pipe_info[2] = skb_pipe_list->data[i];
|
||||
r = nci_hci_send_cmd(ndev, ST21NFCB_DEVICE_MGNT_GATE,
|
||||
ST21NFCB_DM_GETINFO, pipe_info,
|
||||
r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
|
||||
ST_NCI_DM_GETINFO, pipe_info,
|
||||
sizeof(pipe_info), &skb_pipe_info);
|
||||
|
||||
if (r)
|
||||
@ -217,81 +217,81 @@ int st21nfcb_hci_load_session(struct nci_dev *ndev)
|
||||
* - destination hid (1byte)
|
||||
* - destination gid (1byte)
|
||||
*/
|
||||
dm_pipe_info = (struct st21nfcb_pipe_info *)skb_pipe_info->data;
|
||||
if (dm_pipe_info->dst_gate_id == ST21NFCB_APDU_READER_GATE &&
|
||||
dm_pipe_info->src_host_id != ST21NFCB_ESE_HOST_ID) {
|
||||
dm_pipe_info = (struct st_nci_pipe_info *)skb_pipe_info->data;
|
||||
if (dm_pipe_info->dst_gate_id == ST_NCI_APDU_READER_GATE &&
|
||||
dm_pipe_info->src_host_id != ST_NCI_ESE_HOST_ID) {
|
||||
pr_err("Unexpected apdu_reader pipe on host %x\n",
|
||||
dm_pipe_info->src_host_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (j = 0; (j < ARRAY_SIZE(st21nfcb_gates)) &&
|
||||
(st21nfcb_gates[j].gate != dm_pipe_info->dst_gate_id); j++)
|
||||
for (j = 0; (j < ARRAY_SIZE(st_nci_gates)) &&
|
||||
(st_nci_gates[j].gate != dm_pipe_info->dst_gate_id); j++)
|
||||
;
|
||||
|
||||
if (j < ARRAY_SIZE(st21nfcb_gates) &&
|
||||
st21nfcb_gates[j].gate == dm_pipe_info->dst_gate_id &&
|
||||
ST21NFCB_DM_IS_PIPE_OPEN(dm_pipe_info->pipe_state)) {
|
||||
st21nfcb_gates[j].pipe = pipe_info[2];
|
||||
if (j < ARRAY_SIZE(st_nci_gates) &&
|
||||
st_nci_gates[j].gate == dm_pipe_info->dst_gate_id &&
|
||||
ST_NCI_DM_IS_PIPE_OPEN(dm_pipe_info->pipe_state)) {
|
||||
st_nci_gates[j].pipe = pipe_info[2];
|
||||
|
||||
ndev->hci_dev->gate2pipe[st21nfcb_gates[j].gate] =
|
||||
st21nfcb_gates[j].pipe;
|
||||
ndev->hci_dev->pipes[st21nfcb_gates[j].pipe].gate =
|
||||
st21nfcb_gates[j].gate;
|
||||
ndev->hci_dev->pipes[st21nfcb_gates[j].pipe].host =
|
||||
ndev->hci_dev->gate2pipe[st_nci_gates[j].gate] =
|
||||
st_nci_gates[j].pipe;
|
||||
ndev->hci_dev->pipes[st_nci_gates[j].pipe].gate =
|
||||
st_nci_gates[j].gate;
|
||||
ndev->hci_dev->pipes[st_nci_gates[j].pipe].host =
|
||||
dm_pipe_info->src_host_id;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(ndev->hci_dev->init_data.gates, st21nfcb_gates,
|
||||
sizeof(st21nfcb_gates));
|
||||
memcpy(ndev->hci_dev->init_data.gates, st_nci_gates,
|
||||
sizeof(st_nci_gates));
|
||||
|
||||
free_info:
|
||||
kfree_skb(skb_pipe_info);
|
||||
kfree_skb(skb_pipe_list);
|
||||
return r;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(st21nfcb_hci_load_session);
|
||||
EXPORT_SYMBOL_GPL(st_nci_hci_load_session);
|
||||
|
||||
static void st21nfcb_hci_admin_event_received(struct nci_dev *ndev,
|
||||
static void st_nci_hci_admin_event_received(struct nci_dev *ndev,
|
||||
u8 event, struct sk_buff *skb)
|
||||
{
|
||||
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
|
||||
struct st_nci_info *info = nci_get_drvdata(ndev);
|
||||
|
||||
switch (event) {
|
||||
case ST21NFCB_EVT_HOT_PLUG:
|
||||
case ST_NCI_EVT_HOT_PLUG:
|
||||
if (info->se_info.se_active) {
|
||||
if (!ST21NFCB_EVT_HOT_PLUG_IS_INHIBITED(skb)) {
|
||||
if (!ST_NCI_EVT_HOT_PLUG_IS_INHIBITED(skb)) {
|
||||
del_timer_sync(&info->se_info.se_active_timer);
|
||||
info->se_info.se_active = false;
|
||||
complete(&info->se_info.req_completion);
|
||||
} else {
|
||||
mod_timer(&info->se_info.se_active_timer,
|
||||
jiffies +
|
||||
msecs_to_jiffies(ST21NFCB_SE_TO_PIPES));
|
||||
msecs_to_jiffies(ST_NCI_SE_TO_PIPES));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int st21nfcb_hci_apdu_reader_event_received(struct nci_dev *ndev,
|
||||
static int st_nci_hci_apdu_reader_event_received(struct nci_dev *ndev,
|
||||
u8 event,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
int r = 0;
|
||||
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
|
||||
struct st_nci_info *info = nci_get_drvdata(ndev);
|
||||
|
||||
pr_debug("apdu reader gate event: %x\n", event);
|
||||
|
||||
switch (event) {
|
||||
case ST21NFCB_EVT_TRANSMIT_DATA:
|
||||
case ST_NCI_EVT_TRANSMIT_DATA:
|
||||
del_timer_sync(&info->se_info.bwi_timer);
|
||||
info->se_info.bwi_active = false;
|
||||
info->se_info.cb(info->se_info.cb_context,
|
||||
skb->data, skb->len, 0);
|
||||
break;
|
||||
case ST21NFCB_EVT_WTX_REQUEST:
|
||||
case ST_NCI_EVT_WTX_REQUEST:
|
||||
mod_timer(&info->se_info.bwi_timer, jiffies +
|
||||
msecs_to_jiffies(info->se_info.wt_timeout));
|
||||
break;
|
||||
@ -306,7 +306,7 @@ static int st21nfcb_hci_apdu_reader_event_received(struct nci_dev *ndev,
|
||||
* <= 0: driver handled the event, skb consumed
|
||||
* 1: driver does not handle the event, please do standard processing
|
||||
*/
|
||||
static int st21nfcb_hci_connectivity_event_received(struct nci_dev *ndev,
|
||||
static int st_nci_hci_connectivity_event_received(struct nci_dev *ndev,
|
||||
u8 host, u8 event,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
@ -317,10 +317,10 @@ static int st21nfcb_hci_connectivity_event_received(struct nci_dev *ndev,
|
||||
pr_debug("connectivity gate event: %x\n", event);
|
||||
|
||||
switch (event) {
|
||||
case ST21NFCB_EVT_CONNECTIVITY:
|
||||
case ST_NCI_EVT_CONNECTIVITY:
|
||||
|
||||
break;
|
||||
case ST21NFCB_EVT_TRANSACTION:
|
||||
case ST_NCI_EVT_TRANSACTION:
|
||||
/* According to specification etsi 102 622
|
||||
* 11.2.2.4 EVT_TRANSACTION Table 52
|
||||
* Description Tag Length
|
||||
@ -355,7 +355,7 @@ static int st21nfcb_hci_connectivity_event_received(struct nci_dev *ndev,
|
||||
return r;
|
||||
}
|
||||
|
||||
void st21nfcb_hci_event_received(struct nci_dev *ndev, u8 pipe,
|
||||
void st_nci_hci_event_received(struct nci_dev *ndev, u8 pipe,
|
||||
u8 event, struct sk_buff *skb)
|
||||
{
|
||||
u8 gate = ndev->hci_dev->pipes[pipe].gate;
|
||||
@ -363,32 +363,32 @@ void st21nfcb_hci_event_received(struct nci_dev *ndev, u8 pipe,
|
||||
|
||||
switch (gate) {
|
||||
case NCI_HCI_ADMIN_GATE:
|
||||
st21nfcb_hci_admin_event_received(ndev, event, skb);
|
||||
st_nci_hci_admin_event_received(ndev, event, skb);
|
||||
break;
|
||||
case ST21NFCB_APDU_READER_GATE:
|
||||
st21nfcb_hci_apdu_reader_event_received(ndev, event, skb);
|
||||
case ST_NCI_APDU_READER_GATE:
|
||||
st_nci_hci_apdu_reader_event_received(ndev, event, skb);
|
||||
break;
|
||||
case ST21NFCB_CONNECTIVITY_GATE:
|
||||
st21nfcb_hci_connectivity_event_received(ndev, host, event,
|
||||
case ST_NCI_CONNECTIVITY_GATE:
|
||||
st_nci_hci_connectivity_event_received(ndev, host, event,
|
||||
skb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(st21nfcb_hci_event_received);
|
||||
EXPORT_SYMBOL_GPL(st_nci_hci_event_received);
|
||||
|
||||
|
||||
void st21nfcb_hci_cmd_received(struct nci_dev *ndev, u8 pipe, u8 cmd,
|
||||
void st_nci_hci_cmd_received(struct nci_dev *ndev, u8 pipe, u8 cmd,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
|
||||
struct st_nci_info *info = nci_get_drvdata(ndev);
|
||||
u8 gate = ndev->hci_dev->pipes[pipe].gate;
|
||||
|
||||
pr_debug("cmd: %x\n", cmd);
|
||||
|
||||
switch (cmd) {
|
||||
case NCI_HCI_ANY_OPEN_PIPE:
|
||||
if (gate != ST21NFCB_APDU_READER_GATE &&
|
||||
ndev->hci_dev->pipes[pipe].host != ST21NFCB_UICC_HOST_ID)
|
||||
if (gate != ST_NCI_APDU_READER_GATE &&
|
||||
ndev->hci_dev->pipes[pipe].host != ST_NCI_UICC_HOST_ID)
|
||||
ndev->hci_dev->count_pipes++;
|
||||
|
||||
if (ndev->hci_dev->count_pipes ==
|
||||
@ -401,28 +401,28 @@ void st21nfcb_hci_cmd_received(struct nci_dev *ndev, u8 pipe, u8 cmd,
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(st21nfcb_hci_cmd_received);
|
||||
EXPORT_SYMBOL_GPL(st_nci_hci_cmd_received);
|
||||
|
||||
/*
|
||||
* Remarks: On some early st21nfcb firmware, nci_nfcee_mode_set(0)
|
||||
* Remarks: On some early st_nci firmware, nci_nfcee_mode_set(0)
|
||||
* is rejected
|
||||
*/
|
||||
static int st21nfcb_nci_control_se(struct nci_dev *ndev, u8 se_idx,
|
||||
static int st_nci_control_se(struct nci_dev *ndev, u8 se_idx,
|
||||
u8 state)
|
||||
{
|
||||
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
|
||||
struct st_nci_info *info = nci_get_drvdata(ndev);
|
||||
int r;
|
||||
struct sk_buff *sk_host_list;
|
||||
u8 host_id;
|
||||
|
||||
switch (se_idx) {
|
||||
case ST21NFCB_UICC_HOST_ID:
|
||||
case ST_NCI_UICC_HOST_ID:
|
||||
ndev->hci_dev->count_pipes = 0;
|
||||
ndev->hci_dev->expected_pipes = ST21NFCB_SE_COUNT_PIPE_UICC;
|
||||
ndev->hci_dev->expected_pipes = ST_NCI_SE_COUNT_PIPE_UICC;
|
||||
break;
|
||||
case ST21NFCB_ESE_HOST_ID:
|
||||
case ST_NCI_ESE_HOST_ID:
|
||||
ndev->hci_dev->count_pipes = 0;
|
||||
ndev->hci_dev->expected_pipes = ST21NFCB_SE_COUNT_PIPE_EMBEDDED;
|
||||
ndev->hci_dev->expected_pipes = ST_NCI_SE_COUNT_PIPE_EMBEDDED;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
@ -438,7 +438,7 @@ static int st21nfcb_nci_control_se(struct nci_dev *ndev, u8 se_idx,
|
||||
return r;
|
||||
|
||||
mod_timer(&info->se_info.se_active_timer, jiffies +
|
||||
msecs_to_jiffies(ST21NFCB_SE_TO_HOT_PLUG));
|
||||
msecs_to_jiffies(ST_NCI_SE_TO_HOT_PLUG));
|
||||
info->se_info.se_active = true;
|
||||
|
||||
/* Ignore return value and check in any case the host_list */
|
||||
@ -458,49 +458,49 @@ static int st21nfcb_nci_control_se(struct nci_dev *ndev, u8 se_idx,
|
||||
|
||||
host_id = sk_host_list->data[sk_host_list->len - 1];
|
||||
kfree_skb(sk_host_list);
|
||||
if (state == ST21NFCB_SE_MODE_ON && host_id == se_idx)
|
||||
if (state == ST_NCI_SE_MODE_ON && host_id == se_idx)
|
||||
return se_idx;
|
||||
else if (state == ST21NFCB_SE_MODE_OFF && host_id != se_idx)
|
||||
else if (state == ST_NCI_SE_MODE_OFF && host_id != se_idx)
|
||||
return se_idx;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int st21nfcb_nci_disable_se(struct nci_dev *ndev, u32 se_idx)
|
||||
int st_nci_disable_se(struct nci_dev *ndev, u32 se_idx)
|
||||
{
|
||||
int r;
|
||||
|
||||
pr_debug("st21nfcb_nci_disable_se\n");
|
||||
pr_debug("st_nci_disable_se\n");
|
||||
|
||||
if (se_idx == NFC_SE_EMBEDDED) {
|
||||
r = nci_hci_send_event(ndev, ST21NFCB_APDU_READER_GATE,
|
||||
ST21NFCB_EVT_SE_END_OF_APDU_TRANSFER, NULL, 0);
|
||||
r = nci_hci_send_event(ndev, ST_NCI_APDU_READER_GATE,
|
||||
ST_NCI_EVT_SE_END_OF_APDU_TRANSFER, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(st21nfcb_nci_disable_se);
|
||||
EXPORT_SYMBOL_GPL(st_nci_disable_se);
|
||||
|
||||
int st21nfcb_nci_enable_se(struct nci_dev *ndev, u32 se_idx)
|
||||
int st_nci_enable_se(struct nci_dev *ndev, u32 se_idx)
|
||||
{
|
||||
int r;
|
||||
|
||||
pr_debug("st21nfcb_nci_enable_se\n");
|
||||
pr_debug("st_nci_enable_se\n");
|
||||
|
||||
if (se_idx == ST21NFCB_HCI_HOST_ID_ESE) {
|
||||
r = nci_hci_send_event(ndev, ST21NFCB_APDU_READER_GATE,
|
||||
ST21NFCB_EVT_SE_SOFT_RESET, NULL, 0);
|
||||
if (se_idx == ST_NCI_HCI_HOST_ID_ESE) {
|
||||
r = nci_hci_send_event(ndev, ST_NCI_APDU_READER_GATE,
|
||||
ST_NCI_EVT_SE_SOFT_RESET, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(st21nfcb_nci_enable_se);
|
||||
EXPORT_SYMBOL_GPL(st_nci_enable_se);
|
||||
|
||||
static int st21nfcb_hci_network_init(struct nci_dev *ndev)
|
||||
static int st_nci_hci_network_init(struct nci_dev *ndev)
|
||||
{
|
||||
struct core_conn_create_dest_spec_params *dest_params;
|
||||
struct dest_spec_params spec_params;
|
||||
@ -519,7 +519,8 @@ static int st21nfcb_hci_network_init(struct nci_dev *ndev)
|
||||
dest_params->length = sizeof(struct dest_spec_params);
|
||||
spec_params.id = ndev->hci_dev->nfcee_id;
|
||||
spec_params.protocol = NCI_NFCEE_INTERFACE_HCI_ACCESS;
|
||||
memcpy(dest_params->value, &spec_params, sizeof(struct dest_spec_params));
|
||||
memcpy(dest_params->value, &spec_params,
|
||||
sizeof(struct dest_spec_params));
|
||||
r = nci_core_conn_create(ndev, NCI_DESTINATION_NFCEE, 1,
|
||||
sizeof(struct core_conn_create_dest_spec_params) +
|
||||
sizeof(struct dest_spec_params),
|
||||
@ -531,15 +532,15 @@ static int st21nfcb_hci_network_init(struct nci_dev *ndev)
|
||||
if (!conn_info)
|
||||
goto free_dest_params;
|
||||
|
||||
memcpy(ndev->hci_dev->init_data.gates, st21nfcb_gates,
|
||||
sizeof(st21nfcb_gates));
|
||||
memcpy(ndev->hci_dev->init_data.gates, st_nci_gates,
|
||||
sizeof(st_nci_gates));
|
||||
|
||||
/*
|
||||
* Session id must include the driver name + i2c bus addr
|
||||
* persistent info to discriminate 2 identical chips
|
||||
*/
|
||||
dev_num = find_first_zero_bit(dev_mask, ST21NFCB_NUM_DEVICES);
|
||||
if (dev_num >= ST21NFCB_NUM_DEVICES) {
|
||||
dev_num = find_first_zero_bit(dev_mask, ST_NCI_NUM_DEVICES);
|
||||
if (dev_num >= ST_NCI_NUM_DEVICES) {
|
||||
r = -ENODEV;
|
||||
goto free_dest_params;
|
||||
}
|
||||
@ -564,72 +565,72 @@ static int st21nfcb_hci_network_init(struct nci_dev *ndev)
|
||||
return r;
|
||||
}
|
||||
|
||||
int st21nfcb_nci_discover_se(struct nci_dev *ndev)
|
||||
int st_nci_discover_se(struct nci_dev *ndev)
|
||||
{
|
||||
u8 param[2];
|
||||
int r;
|
||||
int se_count = 0;
|
||||
|
||||
pr_debug("st21nfcb_nci_discover_se\n");
|
||||
pr_debug("st_nci_discover_se\n");
|
||||
|
||||
r = st21nfcb_hci_network_init(ndev);
|
||||
r = st_nci_hci_network_init(ndev);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
param[0] = ST21NFCB_UICC_HOST_ID;
|
||||
param[1] = ST21NFCB_HCI_HOST_ID_ESE;
|
||||
param[0] = ST_NCI_UICC_HOST_ID;
|
||||
param[1] = ST_NCI_HCI_HOST_ID_ESE;
|
||||
r = nci_hci_set_param(ndev, NCI_HCI_ADMIN_GATE,
|
||||
NCI_HCI_ADMIN_PARAM_WHITELIST,
|
||||
param, sizeof(param));
|
||||
if (r != NCI_HCI_ANY_OK)
|
||||
return r;
|
||||
|
||||
r = st21nfcb_nci_control_se(ndev, ST21NFCB_UICC_HOST_ID,
|
||||
ST21NFCB_SE_MODE_ON);
|
||||
if (r == ST21NFCB_UICC_HOST_ID) {
|
||||
nfc_add_se(ndev->nfc_dev, ST21NFCB_UICC_HOST_ID, NFC_SE_UICC);
|
||||
r = st_nci_control_se(ndev, ST_NCI_UICC_HOST_ID,
|
||||
ST_NCI_SE_MODE_ON);
|
||||
if (r == ST_NCI_UICC_HOST_ID) {
|
||||
nfc_add_se(ndev->nfc_dev, ST_NCI_UICC_HOST_ID, NFC_SE_UICC);
|
||||
se_count++;
|
||||
}
|
||||
|
||||
/* Try to enable eSE in order to check availability */
|
||||
r = st21nfcb_nci_control_se(ndev, ST21NFCB_HCI_HOST_ID_ESE,
|
||||
ST21NFCB_SE_MODE_ON);
|
||||
if (r == ST21NFCB_HCI_HOST_ID_ESE) {
|
||||
nfc_add_se(ndev->nfc_dev, ST21NFCB_HCI_HOST_ID_ESE,
|
||||
r = st_nci_control_se(ndev, ST_NCI_HCI_HOST_ID_ESE,
|
||||
ST_NCI_SE_MODE_ON);
|
||||
if (r == ST_NCI_HCI_HOST_ID_ESE) {
|
||||
nfc_add_se(ndev->nfc_dev, ST_NCI_HCI_HOST_ID_ESE,
|
||||
NFC_SE_EMBEDDED);
|
||||
se_count++;
|
||||
st21nfcb_se_get_atr(ndev);
|
||||
st_nci_se_get_atr(ndev);
|
||||
}
|
||||
|
||||
return !se_count;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(st21nfcb_nci_discover_se);
|
||||
EXPORT_SYMBOL_GPL(st_nci_discover_se);
|
||||
|
||||
int st21nfcb_nci_se_io(struct nci_dev *ndev, u32 se_idx,
|
||||
int st_nci_se_io(struct nci_dev *ndev, u32 se_idx,
|
||||
u8 *apdu, size_t apdu_length,
|
||||
se_io_cb_t cb, void *cb_context)
|
||||
{
|
||||
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
|
||||
struct st_nci_info *info = nci_get_drvdata(ndev);
|
||||
|
||||
pr_debug("\n");
|
||||
|
||||
switch (se_idx) {
|
||||
case ST21NFCB_HCI_HOST_ID_ESE:
|
||||
case ST_NCI_HCI_HOST_ID_ESE:
|
||||
info->se_info.cb = cb;
|
||||
info->se_info.cb_context = cb_context;
|
||||
mod_timer(&info->se_info.bwi_timer, jiffies +
|
||||
msecs_to_jiffies(info->se_info.wt_timeout));
|
||||
info->se_info.bwi_active = true;
|
||||
return nci_hci_send_event(ndev, ST21NFCB_APDU_READER_GATE,
|
||||
ST21NFCB_EVT_TRANSMIT_DATA, apdu,
|
||||
return nci_hci_send_event(ndev, ST_NCI_APDU_READER_GATE,
|
||||
ST_NCI_EVT_TRANSMIT_DATA, apdu,
|
||||
apdu_length);
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(st21nfcb_nci_se_io);
|
||||
EXPORT_SYMBOL(st_nci_se_io);
|
||||
|
||||
static void st21nfcb_se_wt_timeout(unsigned long data)
|
||||
static void st_nci_se_wt_timeout(unsigned long data)
|
||||
{
|
||||
/*
|
||||
* No answer from the secure element
|
||||
@ -642,7 +643,7 @@ static void st21nfcb_se_wt_timeout(unsigned long data)
|
||||
*/
|
||||
/* hardware reset managed through VCC_UICC_OUT power supply */
|
||||
u8 param = 0x01;
|
||||
struct st21nfcb_nci_info *info = (struct st21nfcb_nci_info *) data;
|
||||
struct st_nci_info *info = (struct st_nci_info *) data;
|
||||
|
||||
pr_debug("\n");
|
||||
|
||||
@ -650,19 +651,19 @@ static void st21nfcb_se_wt_timeout(unsigned long data)
|
||||
|
||||
if (!info->se_info.xch_error) {
|
||||
info->se_info.xch_error = true;
|
||||
nci_hci_send_event(info->ndlc->ndev, ST21NFCB_APDU_READER_GATE,
|
||||
ST21NFCB_EVT_SE_SOFT_RESET, NULL, 0);
|
||||
nci_hci_send_event(info->ndlc->ndev, ST_NCI_APDU_READER_GATE,
|
||||
ST_NCI_EVT_SE_SOFT_RESET, NULL, 0);
|
||||
} else {
|
||||
info->se_info.xch_error = false;
|
||||
nci_hci_send_event(info->ndlc->ndev, ST21NFCB_DEVICE_MGNT_GATE,
|
||||
ST21NFCB_EVT_SE_HARD_RESET, ¶m, 1);
|
||||
nci_hci_send_event(info->ndlc->ndev, ST_NCI_DEVICE_MGNT_GATE,
|
||||
ST_NCI_EVT_SE_HARD_RESET, ¶m, 1);
|
||||
}
|
||||
info->se_info.cb(info->se_info.cb_context, NULL, 0, -ETIME);
|
||||
}
|
||||
|
||||
static void st21nfcb_se_activation_timeout(unsigned long data)
|
||||
static void st_nci_se_activation_timeout(unsigned long data)
|
||||
{
|
||||
struct st21nfcb_nci_info *info = (struct st21nfcb_nci_info *) data;
|
||||
struct st_nci_info *info = (struct st_nci_info *) data;
|
||||
|
||||
pr_debug("\n");
|
||||
|
||||
@ -671,35 +672,35 @@ static void st21nfcb_se_activation_timeout(unsigned long data)
|
||||
complete(&info->se_info.req_completion);
|
||||
}
|
||||
|
||||
int st21nfcb_se_init(struct nci_dev *ndev)
|
||||
int st_nci_se_init(struct nci_dev *ndev)
|
||||
{
|
||||
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
|
||||
struct st_nci_info *info = nci_get_drvdata(ndev);
|
||||
|
||||
init_completion(&info->se_info.req_completion);
|
||||
/* initialize timers */
|
||||
init_timer(&info->se_info.bwi_timer);
|
||||
info->se_info.bwi_timer.data = (unsigned long)info;
|
||||
info->se_info.bwi_timer.function = st21nfcb_se_wt_timeout;
|
||||
info->se_info.bwi_timer.function = st_nci_se_wt_timeout;
|
||||
info->se_info.bwi_active = false;
|
||||
|
||||
init_timer(&info->se_info.se_active_timer);
|
||||
info->se_info.se_active_timer.data = (unsigned long)info;
|
||||
info->se_info.se_active_timer.function =
|
||||
st21nfcb_se_activation_timeout;
|
||||
st_nci_se_activation_timeout;
|
||||
info->se_info.se_active = false;
|
||||
|
||||
info->se_info.xch_error = false;
|
||||
|
||||
info->se_info.wt_timeout =
|
||||
ST21NFCB_BWI_TO_TIMEOUT(ST21NFCB_ATR_DEFAULT_BWI);
|
||||
ST_NCI_BWI_TO_TIMEOUT(ST_NCI_ATR_DEFAULT_BWI);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(st21nfcb_se_init);
|
||||
EXPORT_SYMBOL(st_nci_se_init);
|
||||
|
||||
void st21nfcb_se_deinit(struct nci_dev *ndev)
|
||||
void st_nci_se_deinit(struct nci_dev *ndev)
|
||||
{
|
||||
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
|
||||
struct st_nci_info *info = nci_get_drvdata(ndev);
|
||||
|
||||
if (info->se_info.bwi_active)
|
||||
del_timer_sync(&info->se_info.bwi_timer);
|
||||
@ -709,5 +710,5 @@ void st21nfcb_se_deinit(struct nci_dev *ndev)
|
||||
info->se_info.se_active = false;
|
||||
info->se_info.bwi_active = false;
|
||||
}
|
||||
EXPORT_SYMBOL(st21nfcb_se_deinit);
|
||||
EXPORT_SYMBOL(st_nci_se_deinit);
|
||||
|
61
drivers/nfc/st-nci/st-nci_se.h
Normal file
61
drivers/nfc/st-nci/st-nci_se.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Secure Element Driver for STMicroelectronics NFC NCI Chip
|
||||
*
|
||||
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef __LOCAL_ST_NCI_SE_H_
|
||||
#define __LOCAL_ST_NCI_SE_H_
|
||||
|
||||
/*
|
||||
* ref ISO7816-3 chap 8.1. the initial character TS is followed by a
|
||||
* sequence of at most 32 characters.
|
||||
*/
|
||||
#define ST_NCI_ESE_MAX_LENGTH 33
|
||||
#define ST_NCI_HCI_HOST_ID_ESE 0xc0
|
||||
|
||||
struct st_nci_se_info {
|
||||
u8 atr[ST_NCI_ESE_MAX_LENGTH];
|
||||
struct completion req_completion;
|
||||
|
||||
struct timer_list bwi_timer;
|
||||
int wt_timeout; /* in msecs */
|
||||
bool bwi_active;
|
||||
|
||||
struct timer_list se_active_timer;
|
||||
bool se_active;
|
||||
|
||||
bool xch_error;
|
||||
|
||||
se_io_cb_t cb;
|
||||
void *cb_context;
|
||||
};
|
||||
|
||||
int st_nci_se_init(struct nci_dev *ndev);
|
||||
void st_nci_se_deinit(struct nci_dev *ndev);
|
||||
|
||||
int st_nci_discover_se(struct nci_dev *ndev);
|
||||
int st_nci_enable_se(struct nci_dev *ndev, u32 se_idx);
|
||||
int st_nci_disable_se(struct nci_dev *ndev, u32 se_idx);
|
||||
int st_nci_se_io(struct nci_dev *ndev, u32 se_idx,
|
||||
u8 *apdu, size_t apdu_length,
|
||||
se_io_cb_t cb, void *cb_context);
|
||||
int st_nci_hci_load_session(struct nci_dev *ndev);
|
||||
void st_nci_hci_event_received(struct nci_dev *ndev, u8 pipe,
|
||||
u8 event, struct sk_buff *skb);
|
||||
void st_nci_hci_cmd_received(struct nci_dev *ndev, u8 pipe, u8 cmd,
|
||||
struct sk_buff *skb);
|
||||
|
||||
|
||||
#endif /* __LOCAL_ST_NCI_SE_H_ */
|
@ -1,22 +0,0 @@
|
||||
config NFC_ST21NFCB
|
||||
tristate "STMicroelectronics ST21NFCB NFC driver"
|
||||
depends on NFC_NCI
|
||||
default n
|
||||
---help---
|
||||
STMicroelectronics ST21NFCB core driver. It implements the chipset
|
||||
NCI logic and hooks into the NFC kernel APIs. Physical layers will
|
||||
register against it.
|
||||
|
||||
To compile this driver as a module, choose m here. The module will
|
||||
be called st21nfcb.
|
||||
Say N if unsure.
|
||||
|
||||
config NFC_ST21NFCB_I2C
|
||||
tristate "NFC ST21NFCB i2c support"
|
||||
depends on NFC_ST21NFCB && I2C
|
||||
---help---
|
||||
This module adds support for the STMicroelectronics st21nfcb i2c interface.
|
||||
Select this if your platform is using the i2c bus.
|
||||
|
||||
If you choose to build a module, it'll be called st21nfcb_i2c.
|
||||
Say N if unsure.
|
@ -1,9 +0,0 @@
|
||||
#
|
||||
# Makefile for ST21NFCB NCI based NFC driver
|
||||
#
|
||||
|
||||
st21nfcb_nci-objs = ndlc.o st21nfcb.o st21nfcb_se.o
|
||||
obj-$(CONFIG_NFC_ST21NFCB) += st21nfcb_nci.o
|
||||
|
||||
st21nfcb_i2c-objs = i2c.o
|
||||
obj-$(CONFIG_NFC_ST21NFCB_I2C) += st21nfcb_i2c.o
|
@ -1,143 +0,0 @@
|
||||
/*
|
||||
* NCI based Driver for STMicroelectronics NFC Chip
|
||||
*
|
||||
* Copyright (C) 2014 STMicroelectronics SAS. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/nfc.h>
|
||||
#include <net/nfc/nci.h>
|
||||
#include <net/nfc/nci_core.h>
|
||||
|
||||
#include "st21nfcb.h"
|
||||
#include "st21nfcb_se.h"
|
||||
|
||||
#define DRIVER_DESC "NCI NFC driver for ST21NFCB"
|
||||
|
||||
#define ST21NFCB_NCI1_X_PROPRIETARY_ISO15693 0x83
|
||||
|
||||
static int st21nfcb_nci_open(struct nci_dev *ndev)
|
||||
{
|
||||
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
|
||||
int r;
|
||||
|
||||
if (test_and_set_bit(ST21NFCB_NCI_RUNNING, &info->flags))
|
||||
return 0;
|
||||
|
||||
r = ndlc_open(info->ndlc);
|
||||
if (r)
|
||||
clear_bit(ST21NFCB_NCI_RUNNING, &info->flags);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int st21nfcb_nci_close(struct nci_dev *ndev)
|
||||
{
|
||||
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
|
||||
|
||||
if (!test_and_clear_bit(ST21NFCB_NCI_RUNNING, &info->flags))
|
||||
return 0;
|
||||
|
||||
ndlc_close(info->ndlc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st21nfcb_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
|
||||
{
|
||||
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
|
||||
|
||||
skb->dev = (void *)ndev;
|
||||
|
||||
if (!test_bit(ST21NFCB_NCI_RUNNING, &info->flags))
|
||||
return -EBUSY;
|
||||
|
||||
return ndlc_send(info->ndlc, skb);
|
||||
}
|
||||
|
||||
static __u32 st21nfcb_nci_get_rfprotocol(struct nci_dev *ndev,
|
||||
__u8 rf_protocol)
|
||||
{
|
||||
return rf_protocol == ST21NFCB_NCI1_X_PROPRIETARY_ISO15693 ?
|
||||
NFC_PROTO_ISO15693_MASK : 0;
|
||||
}
|
||||
|
||||
static struct nci_ops st21nfcb_nci_ops = {
|
||||
.open = st21nfcb_nci_open,
|
||||
.close = st21nfcb_nci_close,
|
||||
.send = st21nfcb_nci_send,
|
||||
.get_rfprotocol = st21nfcb_nci_get_rfprotocol,
|
||||
.discover_se = st21nfcb_nci_discover_se,
|
||||
.enable_se = st21nfcb_nci_enable_se,
|
||||
.disable_se = st21nfcb_nci_disable_se,
|
||||
.se_io = st21nfcb_nci_se_io,
|
||||
.hci_load_session = st21nfcb_hci_load_session,
|
||||
.hci_event_received = st21nfcb_hci_event_received,
|
||||
.hci_cmd_received = st21nfcb_hci_cmd_received,
|
||||
};
|
||||
|
||||
int st21nfcb_nci_probe(struct llt_ndlc *ndlc, int phy_headroom,
|
||||
int phy_tailroom)
|
||||
{
|
||||
struct st21nfcb_nci_info *info;
|
||||
int r;
|
||||
u32 protocols;
|
||||
|
||||
info = devm_kzalloc(ndlc->dev,
|
||||
sizeof(struct st21nfcb_nci_info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
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;
|
||||
|
||||
ndlc->ndev = nci_allocate_device(&st21nfcb_nci_ops, protocols,
|
||||
phy_headroom, phy_tailroom);
|
||||
if (!ndlc->ndev) {
|
||||
pr_err("Cannot allocate nfc ndev\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
info->ndlc = ndlc;
|
||||
|
||||
nci_set_drvdata(ndlc->ndev, info);
|
||||
|
||||
r = nci_register_device(ndlc->ndev);
|
||||
if (r) {
|
||||
pr_err("Cannot register nfc device to nci core\n");
|
||||
nci_free_device(ndlc->ndev);
|
||||
return r;
|
||||
}
|
||||
|
||||
return st21nfcb_se_init(ndlc->ndev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(st21nfcb_nci_probe);
|
||||
|
||||
void st21nfcb_nci_remove(struct nci_dev *ndev)
|
||||
{
|
||||
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
|
||||
|
||||
nci_unregister_device(ndev);
|
||||
nci_free_device(ndev);
|
||||
kfree(info);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(st21nfcb_nci_remove);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* NCI based Driver for STMicroelectronics NFC Chip
|
||||
*
|
||||
* Copyright (C) 2014 STMicroelectronics SAS. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef __LOCAL_ST21NFCB_SE_H_
|
||||
#define __LOCAL_ST21NFCB_SE_H_
|
||||
|
||||
/*
|
||||
* ref ISO7816-3 chap 8.1. the initial character TS is followed by a
|
||||
* sequence of at most 32 characters.
|
||||
*/
|
||||
#define ST21NFCB_ESE_MAX_LENGTH 33
|
||||
#define ST21NFCB_HCI_HOST_ID_ESE 0xc0
|
||||
|
||||
struct st21nfcb_se_info {
|
||||
u8 atr[ST21NFCB_ESE_MAX_LENGTH];
|
||||
struct completion req_completion;
|
||||
|
||||
struct timer_list bwi_timer;
|
||||
int wt_timeout; /* in msecs */
|
||||
bool bwi_active;
|
||||
|
||||
struct timer_list se_active_timer;
|
||||
bool se_active;
|
||||
|
||||
bool xch_error;
|
||||
|
||||
se_io_cb_t cb;
|
||||
void *cb_context;
|
||||
};
|
||||
|
||||
int st21nfcb_se_init(struct nci_dev *ndev);
|
||||
void st21nfcb_se_deinit(struct nci_dev *ndev);
|
||||
|
||||
int st21nfcb_nci_discover_se(struct nci_dev *ndev);
|
||||
int st21nfcb_nci_enable_se(struct nci_dev *ndev, u32 se_idx);
|
||||
int st21nfcb_nci_disable_se(struct nci_dev *ndev, u32 se_idx);
|
||||
int st21nfcb_nci_se_io(struct nci_dev *ndev, u32 se_idx,
|
||||
u8 *apdu, size_t apdu_length,
|
||||
se_io_cb_t cb, void *cb_context);
|
||||
int st21nfcb_hci_load_session(struct nci_dev *ndev);
|
||||
void st21nfcb_hci_event_received(struct nci_dev *ndev, u8 pipe,
|
||||
u8 event, struct sk_buff *skb);
|
||||
void st21nfcb_hci_cmd_received(struct nci_dev *ndev, u8 pipe, u8 cmd,
|
||||
struct sk_buff *skb);
|
||||
|
||||
|
||||
#endif /* __LOCAL_ST21NFCB_NCI_H_ */
|
@ -149,6 +149,7 @@
|
||||
*/
|
||||
#define TRF7970A_QUIRK_IRQ_STATUS_READ BIT(0)
|
||||
#define TRF7970A_QUIRK_EN2_MUST_STAY_LOW BIT(1)
|
||||
#define TRF7970A_QUIRK_T5T_RMB_EXTRA_BYTE BIT(2)
|
||||
|
||||
/* Direct commands */
|
||||
#define TRF7970A_CMD_IDLE 0x00
|
||||
@ -446,6 +447,7 @@ struct trf7970a {
|
||||
u8 md_rf_tech;
|
||||
u8 tx_cmd;
|
||||
bool issue_eof;
|
||||
bool adjust_resp_len;
|
||||
int en2_gpio;
|
||||
int en_gpio;
|
||||
struct mutex lock;
|
||||
@ -626,6 +628,11 @@ static void trf7970a_send_upstream(struct trf7970a *trf)
|
||||
trf->aborting = false;
|
||||
}
|
||||
|
||||
if (trf->adjust_resp_len) {
|
||||
skb_trim(trf->rx_skb, trf->rx_skb->len - 1);
|
||||
trf->adjust_resp_len = false;
|
||||
}
|
||||
|
||||
trf->cb(trf->ddev, trf->cb_arg, trf->rx_skb);
|
||||
|
||||
trf->rx_skb = NULL;
|
||||
@ -1429,10 +1436,15 @@ static int trf7970a_per_cmd_config(struct trf7970a *trf, struct sk_buff *skb)
|
||||
trf->iso_ctrl = iso_ctrl;
|
||||
}
|
||||
|
||||
if ((trf->framing == NFC_DIGITAL_FRAMING_ISO15693_T5T) &&
|
||||
trf7970a_is_iso15693_write_or_lock(req[1]) &&
|
||||
(req[0] & ISO15693_REQ_FLAG_OPTION))
|
||||
trf->issue_eof = true;
|
||||
if (trf->framing == NFC_DIGITAL_FRAMING_ISO15693_T5T) {
|
||||
if (trf7970a_is_iso15693_write_or_lock(req[1]) &&
|
||||
(req[0] & ISO15693_REQ_FLAG_OPTION))
|
||||
trf->issue_eof = true;
|
||||
else if ((trf->quirks &
|
||||
TRF7970A_QUIRK_T5T_RMB_EXTRA_BYTE) &&
|
||||
(req[1] == ISO15693_CMD_READ_MULTIPLE_BLOCK))
|
||||
trf->adjust_resp_len = true;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -1992,6 +2004,9 @@ static int trf7970a_probe(struct spi_device *spi)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (of_property_read_bool(np, "t5t-rmb-extra-byte-quirk"))
|
||||
trf->quirks |= TRF7970A_QUIRK_T5T_RMB_EXTRA_BYTE;
|
||||
|
||||
if (of_property_read_bool(np, "irq-status-read-quirk"))
|
||||
trf->quirks |= TRF7970A_QUIRK_IRQ_STATUS_READ;
|
||||
|
||||
|
40
include/linux/platform_data/nfcmrvl.h
Normal file
40
include/linux/platform_data/nfcmrvl.h
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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_PTF_H_
|
||||
#define _NFCMRVL_PTF_H_
|
||||
|
||||
struct nfcmrvl_platform_data {
|
||||
/*
|
||||
* Generic
|
||||
*/
|
||||
|
||||
/* GPIO that is wired to RESET_N signal */
|
||||
unsigned int reset_n_io;
|
||||
/* Tell if transport is muxed in HCI one */
|
||||
unsigned int hci_muxed;
|
||||
|
||||
/*
|
||||
* UART specific
|
||||
*/
|
||||
|
||||
/* Tell if UART needs flow control at init */
|
||||
unsigned int flow_control;
|
||||
/* Tell if firmware supports break control for power management */
|
||||
unsigned int break_control;
|
||||
};
|
||||
|
||||
#endif /* _NFCMRVL_PTF_H_ */
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Driver include for the ST21NFCB NFC chip.
|
||||
* Driver include for ST NCI NFC chip family.
|
||||
*
|
||||
* Copyright (C) 2014 STMicroelectronics SAS. All rights reserved.
|
||||
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
@ -16,14 +16,14 @@
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _ST21NFCB_NCI_H_
|
||||
#define _ST21NFCB_NCI_H_
|
||||
#ifndef _ST_NCI_H_
|
||||
#define _ST_NCI_H_
|
||||
|
||||
#define ST21NFCB_NCI_DRIVER_NAME "st21nfcb_nci"
|
||||
#define ST_NCI_DRIVER_NAME "st_nci"
|
||||
|
||||
struct st21nfcb_nfc_platform_data {
|
||||
struct st_nci_nfc_platform_data {
|
||||
unsigned int gpio_reset;
|
||||
unsigned int irq_polarity;
|
||||
};
|
||||
|
||||
#endif /* _ST21NFCB_NCI_H_ */
|
||||
#endif /* _ST_NCI_H_ */
|
29
include/linux/platform_data/st_nci.h
Normal file
29
include/linux/platform_data/st_nci.h
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Driver include for ST NCI NFC chip family.
|
||||
*
|
||||
* Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _ST_NCI_H_
|
||||
#define _ST_NCI_H_
|
||||
|
||||
#define ST_NCI_DRIVER_NAME "st_nci"
|
||||
|
||||
struct st_nci_nfc_platform_data {
|
||||
unsigned int gpio_reset;
|
||||
unsigned int irq_polarity;
|
||||
};
|
||||
|
||||
#endif /* _ST_NCI_H_ */
|
@ -179,6 +179,13 @@ void nfc_hci_unregister_device(struct nfc_hci_dev *hdev);
|
||||
void nfc_hci_set_clientdata(struct nfc_hci_dev *hdev, void *clientdata);
|
||||
void *nfc_hci_get_clientdata(struct nfc_hci_dev *hdev);
|
||||
|
||||
static inline int nfc_hci_set_vendor_cmds(struct nfc_hci_dev *hdev,
|
||||
struct nfc_vendor_cmd *cmds,
|
||||
int n_cmds)
|
||||
{
|
||||
return nfc_set_vendor_cmds(hdev->ndev, cmds, n_cmds);
|
||||
}
|
||||
|
||||
void nfc_hci_driver_failure(struct nfc_hci_dev *hdev, int err);
|
||||
|
||||
int nfc_hci_result_to_errno(u8 result);
|
||||
|
@ -35,6 +35,7 @@
|
||||
#define NCI_MAX_NUM_RF_CONFIGS 10
|
||||
#define NCI_MAX_NUM_CONN 10
|
||||
#define NCI_MAX_PARAM_LEN 251
|
||||
#define NCI_MAX_PACKET_SIZE 258
|
||||
|
||||
/* NCI Status Codes */
|
||||
#define NCI_STATUS_OK 0x00
|
||||
|
@ -31,6 +31,7 @@
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/tty.h>
|
||||
|
||||
#include <net/nfc/nfc.h>
|
||||
#include <net/nfc/nci.h>
|
||||
@ -66,7 +67,14 @@ enum nci_state {
|
||||
|
||||
struct nci_dev;
|
||||
|
||||
struct nci_prop_ops {
|
||||
__u16 opcode;
|
||||
int (*rsp)(struct nci_dev *dev, struct sk_buff *skb);
|
||||
int (*ntf)(struct nci_dev *dev, struct sk_buff *skb);
|
||||
};
|
||||
|
||||
struct nci_ops {
|
||||
int (*init)(struct nci_dev *ndev);
|
||||
int (*open)(struct nci_dev *ndev);
|
||||
int (*close)(struct nci_dev *ndev);
|
||||
int (*send)(struct nci_dev *ndev, struct sk_buff *skb);
|
||||
@ -84,12 +92,16 @@ struct nci_ops {
|
||||
struct sk_buff *skb);
|
||||
void (*hci_cmd_received)(struct nci_dev *ndev, u8 pipe, u8 cmd,
|
||||
struct sk_buff *skb);
|
||||
|
||||
struct nci_prop_ops *prop_ops;
|
||||
size_t n_prop_ops;
|
||||
};
|
||||
|
||||
#define NCI_MAX_SUPPORTED_RF_INTERFACES 4
|
||||
#define NCI_MAX_DISCOVERED_TARGETS 10
|
||||
#define NCI_MAX_NUM_NFCEE 255
|
||||
#define NCI_MAX_CONN_ID 7
|
||||
#define NCI_MAX_PROPRIETARY_CMD 64
|
||||
|
||||
struct nci_conn_info {
|
||||
struct list_head list;
|
||||
@ -264,6 +276,8 @@ int nci_request(struct nci_dev *ndev,
|
||||
void (*req)(struct nci_dev *ndev,
|
||||
unsigned long opt),
|
||||
unsigned long opt, __u32 timeout);
|
||||
int nci_prop_cmd(struct nci_dev *ndev, __u8 oid, size_t len, __u8 *payload);
|
||||
|
||||
int nci_recv_frame(struct nci_dev *ndev, struct sk_buff *skb);
|
||||
int nci_set_config(struct nci_dev *ndev, __u8 id, size_t len, __u8 *val);
|
||||
|
||||
@ -318,8 +332,19 @@ static inline void *nci_get_drvdata(struct nci_dev *ndev)
|
||||
return ndev->driver_data;
|
||||
}
|
||||
|
||||
static inline int nci_set_vendor_cmds(struct nci_dev *ndev,
|
||||
struct nfc_vendor_cmd *cmds,
|
||||
int n_cmds)
|
||||
{
|
||||
return nfc_set_vendor_cmds(ndev->nfc_dev, cmds, n_cmds);
|
||||
}
|
||||
|
||||
void nci_rsp_packet(struct nci_dev *ndev, struct sk_buff *skb);
|
||||
void nci_ntf_packet(struct nci_dev *ndev, struct sk_buff *skb);
|
||||
int nci_prop_rsp_packet(struct nci_dev *ndev, __u16 opcode,
|
||||
struct sk_buff *skb);
|
||||
int nci_prop_ntf_packet(struct nci_dev *ndev, __u16 opcode,
|
||||
struct sk_buff *skb);
|
||||
void nci_rx_data_packet(struct nci_dev *ndev, struct sk_buff *skb);
|
||||
int nci_send_cmd(struct nci_dev *ndev, __u16 opcode, __u8 plen, void *payload);
|
||||
int nci_send_data(struct nci_dev *ndev, __u8 conn_id, struct sk_buff *skb);
|
||||
@ -367,4 +392,50 @@ int nci_spi_send(struct nci_spi *nspi,
|
||||
struct sk_buff *skb);
|
||||
struct sk_buff *nci_spi_read(struct nci_spi *nspi);
|
||||
|
||||
/* ----- NCI UART ---- */
|
||||
|
||||
/* Ioctl */
|
||||
#define NCIUARTSETDRIVER _IOW('U', 0, char *)
|
||||
|
||||
enum nci_uart_driver {
|
||||
NCI_UART_DRIVER_MARVELL = 0,
|
||||
NCI_UART_DRIVER_MAX
|
||||
};
|
||||
|
||||
struct nci_uart;
|
||||
|
||||
struct nci_uart_ops {
|
||||
int (*open)(struct nci_uart *nci_uart);
|
||||
void (*close)(struct nci_uart *nci_uart);
|
||||
int (*recv)(struct nci_uart *nci_uart, struct sk_buff *skb);
|
||||
int (*recv_buf)(struct nci_uart *nci_uart, const u8 *data, char *flags,
|
||||
int count);
|
||||
int (*send)(struct nci_uart *nci_uart, struct sk_buff *skb);
|
||||
void (*tx_start)(struct nci_uart *nci_uart);
|
||||
void (*tx_done)(struct nci_uart *nci_uart);
|
||||
};
|
||||
|
||||
struct nci_uart {
|
||||
struct module *owner;
|
||||
struct nci_uart_ops ops;
|
||||
const char *name;
|
||||
enum nci_uart_driver driver;
|
||||
|
||||
/* Dynamic data */
|
||||
struct nci_dev *ndev;
|
||||
spinlock_t rx_lock;
|
||||
struct work_struct write_work;
|
||||
struct tty_struct *tty;
|
||||
unsigned long tx_state;
|
||||
struct sk_buff_head tx_q;
|
||||
struct sk_buff *tx_skb;
|
||||
struct sk_buff *rx_skb;
|
||||
int rx_packet_len;
|
||||
void *drv_data;
|
||||
};
|
||||
|
||||
int nci_uart_register(struct nci_uart *nu);
|
||||
void nci_uart_unregister(struct nci_uart *nu);
|
||||
void nci_uart_set_config(struct nci_uart *nu, int baudrate, int flow_ctrl);
|
||||
|
||||
#endif /* __NCI_CORE_H */
|
||||
|
@ -165,6 +165,12 @@ struct nfc_genl_data {
|
||||
struct mutex genl_data_mutex;
|
||||
};
|
||||
|
||||
struct nfc_vendor_cmd {
|
||||
__u32 vendor_id;
|
||||
__u32 subcmd;
|
||||
int (*doit)(struct nfc_dev *dev, void *data, size_t data_len);
|
||||
};
|
||||
|
||||
struct nfc_dev {
|
||||
int idx;
|
||||
u32 target_next_idx;
|
||||
@ -193,6 +199,9 @@ struct nfc_dev {
|
||||
|
||||
struct rfkill *rfkill;
|
||||
|
||||
struct nfc_vendor_cmd *vendor_cmds;
|
||||
int n_vendor_cmds;
|
||||
|
||||
struct nfc_ops *ops;
|
||||
};
|
||||
#define to_nfc_dev(_dev) container_of(_dev, struct nfc_dev, dev)
|
||||
@ -296,4 +305,17 @@ struct nfc_se *nfc_find_se(struct nfc_dev *dev, u32 se_idx);
|
||||
void nfc_send_to_raw_sock(struct nfc_dev *dev, struct sk_buff *skb,
|
||||
u8 payload_type, u8 direction);
|
||||
|
||||
static inline int nfc_set_vendor_cmds(struct nfc_dev *dev,
|
||||
struct nfc_vendor_cmd *cmds,
|
||||
int n_cmds)
|
||||
{
|
||||
if (dev->vendor_cmds || dev->n_vendor_cmds)
|
||||
return -EINVAL;
|
||||
|
||||
dev->vendor_cmds = cmds;
|
||||
dev->n_vendor_cmds = n_cmds;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* __NET_NFC_H */
|
||||
|
@ -86,6 +86,8 @@
|
||||
* for this event is the application ID (AID).
|
||||
* @NFC_CMD_GET_SE: Dump all discovered secure elements from an NFC controller.
|
||||
* @NFC_CMD_SE_IO: Send/Receive APDUs to/from the selected secure element.
|
||||
* @NFC_CMD_VENDOR: Vendor specific command, to be implemented directly
|
||||
* from the driver in order to support hardware specific operations.
|
||||
*/
|
||||
enum nfc_commands {
|
||||
NFC_CMD_UNSPEC,
|
||||
@ -117,6 +119,7 @@ enum nfc_commands {
|
||||
NFC_CMD_GET_SE,
|
||||
NFC_CMD_SE_IO,
|
||||
NFC_CMD_ACTIVATE_TARGET,
|
||||
NFC_CMD_VENDOR,
|
||||
/* private: internal use only */
|
||||
__NFC_CMD_AFTER_LAST
|
||||
};
|
||||
@ -153,6 +156,10 @@ enum nfc_commands {
|
||||
* @NFC_ATTR_APDU: Secure element APDU
|
||||
* @NFC_ATTR_TARGET_ISO15693_DSFID: ISO 15693 Data Storage Format Identifier
|
||||
* @NFC_ATTR_TARGET_ISO15693_UID: ISO 15693 Unique Identifier
|
||||
* @NFC_ATTR_VENDOR_ID: NFC manufacturer unique ID, typically an OUI
|
||||
* @NFC_ATTR_VENDOR_SUBCMD: Vendor specific sub command
|
||||
* @NFC_ATTR_VENDOR_DATA: Vendor specific data, to be optionally passed
|
||||
* to a vendor specific command implementation
|
||||
*/
|
||||
enum nfc_attrs {
|
||||
NFC_ATTR_UNSPEC,
|
||||
@ -184,6 +191,9 @@ enum nfc_attrs {
|
||||
NFC_ATTR_TARGET_ISO15693_DSFID,
|
||||
NFC_ATTR_TARGET_ISO15693_UID,
|
||||
NFC_ATTR_SE_PARAMS,
|
||||
NFC_ATTR_VENDOR_ID,
|
||||
NFC_ATTR_VENDOR_SUBCMD,
|
||||
NFC_ATTR_VENDOR_DATA,
|
||||
/* private: internal use only */
|
||||
__NFC_ATTR_AFTER_LAST
|
||||
};
|
||||
|
@ -34,5 +34,6 @@
|
||||
#define N_TI_WL 22 /* for TI's WL BT, FM, GPS combo chips */
|
||||
#define N_TRACESINK 23 /* Trace data routing for MIPI P1149.7 */
|
||||
#define N_TRACEROUTER 24 /* Trace data routing for MIPI P1149.7 */
|
||||
#define N_NCI 25 /* NFC NCI UART */
|
||||
|
||||
#endif /* _UAPI_LINUX_TTY_H */
|
||||
|
@ -19,3 +19,10 @@ config NFC_NCI_SPI
|
||||
an NFC Controller (NFCC) and a Device Host (DH).
|
||||
|
||||
Say yes if you use an NCI driver that requires SPI link layer.
|
||||
|
||||
config NFC_NCI_UART
|
||||
depends on NFC_NCI && TTY
|
||||
tristate "NCI over UART protocol support"
|
||||
default n
|
||||
help
|
||||
Say yes if you use an NCI driver that requires UART link layer.
|
||||
|
@ -7,3 +7,6 @@ obj-$(CONFIG_NFC_NCI) += nci.o
|
||||
nci-objs := core.o data.o lib.o ntf.o rsp.o hci.o
|
||||
|
||||
nci-$(CONFIG_NFC_NCI_SPI) += spi.o
|
||||
|
||||
nci_uart-y += uart.o
|
||||
obj-$(CONFIG_NFC_NCI_UART) += nci_uart.o
|
||||
|
@ -28,6 +28,7 @@
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/completion.h>
|
||||
@ -73,6 +74,7 @@ void nci_req_complete(struct nci_dev *ndev, int result)
|
||||
complete(&ndev->req_completion);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(nci_req_complete);
|
||||
|
||||
static void nci_req_cancel(struct nci_dev *ndev, int err)
|
||||
{
|
||||
@ -323,6 +325,32 @@ static void nci_rf_deactivate_req(struct nci_dev *ndev, unsigned long opt)
|
||||
sizeof(struct nci_rf_deactivate_cmd), &cmd);
|
||||
}
|
||||
|
||||
struct nci_prop_cmd_param {
|
||||
__u16 opcode;
|
||||
size_t len;
|
||||
__u8 *payload;
|
||||
};
|
||||
|
||||
static void nci_prop_cmd_req(struct nci_dev *ndev, unsigned long opt)
|
||||
{
|
||||
struct nci_prop_cmd_param *param = (struct nci_prop_cmd_param *)opt;
|
||||
|
||||
nci_send_cmd(ndev, param->opcode, param->len, param->payload);
|
||||
}
|
||||
|
||||
int nci_prop_cmd(struct nci_dev *ndev, __u8 oid, size_t len, __u8 *payload)
|
||||
{
|
||||
struct nci_prop_cmd_param param;
|
||||
|
||||
param.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, oid);
|
||||
param.len = len;
|
||||
param.payload = payload;
|
||||
|
||||
return __nci_request(ndev, nci_prop_cmd_req, (unsigned long)¶m,
|
||||
msecs_to_jiffies(NCI_CMD_TIMEOUT));
|
||||
}
|
||||
EXPORT_SYMBOL(nci_prop_cmd);
|
||||
|
||||
static int nci_open_device(struct nci_dev *ndev)
|
||||
{
|
||||
int rc = 0;
|
||||
@ -343,11 +371,17 @@ static int nci_open_device(struct nci_dev *ndev)
|
||||
|
||||
set_bit(NCI_INIT, &ndev->flags);
|
||||
|
||||
rc = __nci_request(ndev, nci_reset_req, 0,
|
||||
msecs_to_jiffies(NCI_RESET_TIMEOUT));
|
||||
if (ndev->ops->init)
|
||||
rc = ndev->ops->init(ndev);
|
||||
|
||||
if (ndev->ops->setup)
|
||||
ndev->ops->setup(ndev);
|
||||
if (!rc) {
|
||||
rc = __nci_request(ndev, nci_reset_req, 0,
|
||||
msecs_to_jiffies(NCI_RESET_TIMEOUT));
|
||||
}
|
||||
|
||||
if (!rc && ndev->ops->setup) {
|
||||
rc = ndev->ops->setup(ndev);
|
||||
}
|
||||
|
||||
if (!rc) {
|
||||
rc = __nci_request(ndev, nci_init_req, 0,
|
||||
@ -407,6 +441,12 @@ static int nci_close_device(struct nci_dev *ndev)
|
||||
set_bit(NCI_INIT, &ndev->flags);
|
||||
__nci_request(ndev, nci_reset_req, 0,
|
||||
msecs_to_jiffies(NCI_RESET_TIMEOUT));
|
||||
|
||||
/* After this point our queues are empty
|
||||
* and no works are scheduled.
|
||||
*/
|
||||
ndev->ops->close(ndev);
|
||||
|
||||
clear_bit(NCI_INIT, &ndev->flags);
|
||||
|
||||
del_timer_sync(&ndev->cmd_timer);
|
||||
@ -414,10 +454,6 @@ static int nci_close_device(struct nci_dev *ndev)
|
||||
/* Flush cmd wq */
|
||||
flush_workqueue(ndev->cmd_wq);
|
||||
|
||||
/* After this point our queues are empty
|
||||
* and no works are scheduled. */
|
||||
ndev->ops->close(ndev);
|
||||
|
||||
/* Clear flags */
|
||||
ndev->flags = 0;
|
||||
|
||||
@ -762,7 +798,7 @@ static void nci_deactivate_target(struct nfc_dev *nfc_dev,
|
||||
|
||||
if (atomic_read(&ndev->state) == NCI_POLL_ACTIVE) {
|
||||
nci_request(ndev, nci_rf_deactivate_req,
|
||||
NCI_DEACTIVATE_TYPE_SLEEP_MODE,
|
||||
NCI_DEACTIVATE_TYPE_IDLE_MODE,
|
||||
msecs_to_jiffies(NCI_RF_DEACTIVATE_TIMEOUT));
|
||||
}
|
||||
}
|
||||
@ -961,6 +997,14 @@ struct nci_dev *nci_allocate_device(struct nci_ops *ops,
|
||||
return NULL;
|
||||
|
||||
ndev->ops = ops;
|
||||
|
||||
if (ops->n_prop_ops > NCI_MAX_PROPRIETARY_CMD) {
|
||||
pr_err("Too many proprietary commands: %zd\n",
|
||||
ops->n_prop_ops);
|
||||
ops->prop_ops = NULL;
|
||||
ops->n_prop_ops = 0;
|
||||
}
|
||||
|
||||
ndev->tx_headroom = tx_headroom;
|
||||
ndev->tx_tailroom = tx_tailroom;
|
||||
init_completion(&ndev->req_completion);
|
||||
@ -1165,6 +1209,49 @@ int nci_send_cmd(struct nci_dev *ndev, __u16 opcode, __u8 plen, void *payload)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Proprietary commands API */
|
||||
static struct nci_prop_ops *prop_cmd_lookup(struct nci_dev *ndev,
|
||||
__u16 opcode)
|
||||
{
|
||||
size_t i;
|
||||
struct nci_prop_ops *prop_op;
|
||||
|
||||
if (!ndev->ops->prop_ops || !ndev->ops->n_prop_ops)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < ndev->ops->n_prop_ops; i++) {
|
||||
prop_op = &ndev->ops->prop_ops[i];
|
||||
if (prop_op->opcode == opcode)
|
||||
return prop_op;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int nci_prop_rsp_packet(struct nci_dev *ndev, __u16 rsp_opcode,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
struct nci_prop_ops *prop_op;
|
||||
|
||||
prop_op = prop_cmd_lookup(ndev, rsp_opcode);
|
||||
if (!prop_op || !prop_op->rsp)
|
||||
return -ENOTSUPP;
|
||||
|
||||
return prop_op->rsp(ndev, skb);
|
||||
}
|
||||
|
||||
int nci_prop_ntf_packet(struct nci_dev *ndev, __u16 ntf_opcode,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
struct nci_prop_ops *prop_op;
|
||||
|
||||
prop_op = prop_cmd_lookup(ndev, ntf_opcode);
|
||||
if (!prop_op || !prop_op->ntf)
|
||||
return -ENOTSUPP;
|
||||
|
||||
return prop_op->ntf(ndev, skb);
|
||||
}
|
||||
|
||||
/* ---- NCI TX Data worker thread ---- */
|
||||
|
||||
static void nci_tx_work(struct work_struct *work)
|
||||
|
@ -639,22 +639,19 @@ int nci_hci_dev_session_init(struct nci_dev *ndev)
|
||||
ndev->hci_dev->init_data.gates[0].gate,
|
||||
ndev->hci_dev->init_data.gates[0].pipe);
|
||||
if (r < 0)
|
||||
goto exit;
|
||||
return r;
|
||||
|
||||
r = nci_hci_get_param(ndev, NCI_HCI_ADMIN_GATE,
|
||||
NCI_HCI_ADMIN_PARAM_SESSION_IDENTITY, &skb);
|
||||
if (r < 0)
|
||||
goto exit;
|
||||
return r;
|
||||
|
||||
if (skb->len &&
|
||||
skb->len == strlen(ndev->hci_dev->init_data.session_id) &&
|
||||
memcmp(ndev->hci_dev->init_data.session_id,
|
||||
skb->data, skb->len) == 0 &&
|
||||
!memcmp(ndev->hci_dev->init_data.session_id, skb->data, skb->len) &&
|
||||
ndev->ops->hci_load_session) {
|
||||
/* Restore gate<->pipe table from some proprietary location. */
|
||||
r = ndev->ops->hci_load_session(ndev);
|
||||
if (r < 0)
|
||||
goto exit;
|
||||
} else {
|
||||
r = nci_hci_dev_connect_gates(ndev,
|
||||
ndev->hci_dev->init_data.gate_count,
|
||||
@ -667,8 +664,6 @@ int nci_hci_dev_session_init(struct nci_dev *ndev)
|
||||
ndev->hci_dev->init_data.session_id,
|
||||
strlen(ndev->hci_dev->init_data.session_id));
|
||||
}
|
||||
if (r == 0)
|
||||
goto exit;
|
||||
|
||||
exit:
|
||||
kfree_skb(skb);
|
||||
|
@ -758,6 +758,15 @@ void nci_ntf_packet(struct nci_dev *ndev, struct sk_buff *skb)
|
||||
/* strip the nci control header */
|
||||
skb_pull(skb, NCI_CTRL_HDR_SIZE);
|
||||
|
||||
if (nci_opcode_gid(ntf_opcode) == NCI_GID_PROPRIETARY) {
|
||||
if (nci_prop_ntf_packet(ndev, ntf_opcode, skb)) {
|
||||
pr_err("unsupported ntf opcode 0x%x\n",
|
||||
ntf_opcode);
|
||||
}
|
||||
|
||||
goto end;
|
||||
}
|
||||
|
||||
switch (ntf_opcode) {
|
||||
case NCI_OP_CORE_CONN_CREDITS_NTF:
|
||||
nci_core_conn_credits_ntf_packet(ndev, skb);
|
||||
@ -796,5 +805,6 @@ void nci_ntf_packet(struct nci_dev *ndev, struct sk_buff *skb)
|
||||
break;
|
||||
}
|
||||
|
||||
end:
|
||||
kfree_skb(skb);
|
||||
}
|
||||
|
@ -296,6 +296,15 @@ void nci_rsp_packet(struct nci_dev *ndev, struct sk_buff *skb)
|
||||
/* strip the nci control header */
|
||||
skb_pull(skb, NCI_CTRL_HDR_SIZE);
|
||||
|
||||
if (nci_opcode_gid(rsp_opcode) == NCI_GID_PROPRIETARY) {
|
||||
if (nci_prop_rsp_packet(ndev, rsp_opcode, skb) == -ENOTSUPP) {
|
||||
pr_err("unsupported rsp opcode 0x%x\n",
|
||||
rsp_opcode);
|
||||
}
|
||||
|
||||
goto end;
|
||||
}
|
||||
|
||||
switch (rsp_opcode) {
|
||||
case NCI_OP_CORE_RESET_RSP:
|
||||
nci_core_reset_rsp_packet(ndev, skb);
|
||||
@ -346,6 +355,7 @@ void nci_rsp_packet(struct nci_dev *ndev, struct sk_buff *skb)
|
||||
break;
|
||||
}
|
||||
|
||||
end:
|
||||
kfree_skb(skb);
|
||||
|
||||
/* trigger the next cmd */
|
||||
|
494
net/nfc/nci/uart.c
Normal file
494
net/nfc/nci/uart.c
Normal file
@ -0,0 +1,494 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* Inspired (hugely) by HCI LDISC implementation in Bluetooth.
|
||||
*
|
||||
* Copyright (C) 2000-2001 Qualcomm Incorporated
|
||||
* Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
|
||||
* Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/fcntl.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/poll.h>
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/signal.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/skbuff.h>
|
||||
|
||||
#include <net/nfc/nci.h>
|
||||
#include <net/nfc/nci_core.h>
|
||||
|
||||
/* TX states */
|
||||
#define NCI_UART_SENDING 1
|
||||
#define NCI_UART_TX_WAKEUP 2
|
||||
|
||||
static struct nci_uart *nci_uart_drivers[NCI_UART_DRIVER_MAX];
|
||||
|
||||
static inline struct sk_buff *nci_uart_dequeue(struct nci_uart *nu)
|
||||
{
|
||||
struct sk_buff *skb = nu->tx_skb;
|
||||
|
||||
if (!skb)
|
||||
skb = skb_dequeue(&nu->tx_q);
|
||||
else
|
||||
nu->tx_skb = NULL;
|
||||
|
||||
return skb;
|
||||
}
|
||||
|
||||
static inline int nci_uart_queue_empty(struct nci_uart *nu)
|
||||
{
|
||||
if (nu->tx_skb)
|
||||
return 0;
|
||||
|
||||
return skb_queue_empty(&nu->tx_q);
|
||||
}
|
||||
|
||||
static int nci_uart_tx_wakeup(struct nci_uart *nu)
|
||||
{
|
||||
if (test_and_set_bit(NCI_UART_SENDING, &nu->tx_state)) {
|
||||
set_bit(NCI_UART_TX_WAKEUP, &nu->tx_state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
schedule_work(&nu->write_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nci_uart_write_work(struct work_struct *work)
|
||||
{
|
||||
struct nci_uart *nu = container_of(work, struct nci_uart, write_work);
|
||||
struct tty_struct *tty = nu->tty;
|
||||
struct sk_buff *skb;
|
||||
|
||||
restart:
|
||||
clear_bit(NCI_UART_TX_WAKEUP, &nu->tx_state);
|
||||
|
||||
if (nu->ops.tx_start)
|
||||
nu->ops.tx_start(nu);
|
||||
|
||||
while ((skb = nci_uart_dequeue(nu))) {
|
||||
int len;
|
||||
|
||||
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
|
||||
len = tty->ops->write(tty, skb->data, skb->len);
|
||||
skb_pull(skb, len);
|
||||
if (skb->len) {
|
||||
nu->tx_skb = skb;
|
||||
break;
|
||||
}
|
||||
kfree_skb(skb);
|
||||
}
|
||||
|
||||
if (test_bit(NCI_UART_TX_WAKEUP, &nu->tx_state))
|
||||
goto restart;
|
||||
|
||||
if (nu->ops.tx_done && nci_uart_queue_empty(nu))
|
||||
nu->ops.tx_done(nu);
|
||||
|
||||
clear_bit(NCI_UART_SENDING, &nu->tx_state);
|
||||
}
|
||||
|
||||
static int nci_uart_set_driver(struct tty_struct *tty, unsigned int driver)
|
||||
{
|
||||
struct nci_uart *nu = NULL;
|
||||
int ret;
|
||||
|
||||
if (driver >= NCI_UART_DRIVER_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
if (!nci_uart_drivers[driver])
|
||||
return -ENOENT;
|
||||
|
||||
nu = kzalloc(sizeof(*nu), GFP_KERNEL);
|
||||
if (!nu)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(nu, nci_uart_drivers[driver], sizeof(struct nci_uart));
|
||||
nu->tty = tty;
|
||||
tty->disc_data = nu;
|
||||
skb_queue_head_init(&nu->tx_q);
|
||||
INIT_WORK(&nu->write_work, nci_uart_write_work);
|
||||
spin_lock_init(&nu->rx_lock);
|
||||
|
||||
ret = nu->ops.open(nu);
|
||||
if (ret) {
|
||||
tty->disc_data = NULL;
|
||||
kfree(nu);
|
||||
} else if (!try_module_get(nu->owner)) {
|
||||
nu->ops.close(nu);
|
||||
tty->disc_data = NULL;
|
||||
kfree(nu);
|
||||
return -ENOENT;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ------ LDISC part ------ */
|
||||
|
||||
/* nci_uart_tty_open
|
||||
*
|
||||
* Called when line discipline changed to NCI_UART.
|
||||
*
|
||||
* Arguments:
|
||||
* tty pointer to tty info structure
|
||||
* Return Value:
|
||||
* 0 if success, otherwise error code
|
||||
*/
|
||||
static int nci_uart_tty_open(struct tty_struct *tty)
|
||||
{
|
||||
/* Error if the tty has no write op instead of leaving an exploitable
|
||||
* hole
|
||||
*/
|
||||
if (!tty->ops->write)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
tty->disc_data = NULL;
|
||||
tty->receive_room = 65536;
|
||||
|
||||
/* Flush any pending characters in the driver and line discipline. */
|
||||
|
||||
/* FIXME: why is this needed. Note don't use ldisc_ref here as the
|
||||
* open path is before the ldisc is referencable.
|
||||
*/
|
||||
|
||||
if (tty->ldisc->ops->flush_buffer)
|
||||
tty->ldisc->ops->flush_buffer(tty);
|
||||
tty_driver_flush_buffer(tty);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* nci_uart_tty_close()
|
||||
*
|
||||
* Called when the line discipline is changed to something
|
||||
* else, the tty is closed, or the tty detects a hangup.
|
||||
*/
|
||||
static void nci_uart_tty_close(struct tty_struct *tty)
|
||||
{
|
||||
struct nci_uart *nu = (void *)tty->disc_data;
|
||||
|
||||
/* Detach from the tty */
|
||||
tty->disc_data = NULL;
|
||||
|
||||
if (!nu)
|
||||
return;
|
||||
|
||||
if (nu->tx_skb)
|
||||
kfree_skb(nu->tx_skb);
|
||||
if (nu->rx_skb)
|
||||
kfree_skb(nu->rx_skb);
|
||||
|
||||
skb_queue_purge(&nu->tx_q);
|
||||
|
||||
nu->ops.close(nu);
|
||||
nu->tty = NULL;
|
||||
module_put(nu->owner);
|
||||
|
||||
cancel_work_sync(&nu->write_work);
|
||||
|
||||
kfree(nu);
|
||||
}
|
||||
|
||||
/* nci_uart_tty_wakeup()
|
||||
*
|
||||
* Callback for transmit wakeup. Called when low level
|
||||
* device driver can accept more send data.
|
||||
*
|
||||
* Arguments: tty pointer to associated tty instance data
|
||||
* Return Value: None
|
||||
*/
|
||||
static void nci_uart_tty_wakeup(struct tty_struct *tty)
|
||||
{
|
||||
struct nci_uart *nu = (void *)tty->disc_data;
|
||||
|
||||
if (!nu)
|
||||
return;
|
||||
|
||||
clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
|
||||
|
||||
if (tty != nu->tty)
|
||||
return;
|
||||
|
||||
nci_uart_tx_wakeup(nu);
|
||||
}
|
||||
|
||||
/* nci_uart_tty_receive()
|
||||
*
|
||||
* Called by tty low level driver when receive data is
|
||||
* available.
|
||||
*
|
||||
* Arguments: tty pointer to tty isntance data
|
||||
* data pointer to received data
|
||||
* flags pointer to flags for data
|
||||
* count count of received data in bytes
|
||||
*
|
||||
* Return Value: None
|
||||
*/
|
||||
static void nci_uart_tty_receive(struct tty_struct *tty, const u8 *data,
|
||||
char *flags, int count)
|
||||
{
|
||||
struct nci_uart *nu = (void *)tty->disc_data;
|
||||
|
||||
if (!nu || tty != nu->tty)
|
||||
return;
|
||||
|
||||
spin_lock(&nu->rx_lock);
|
||||
nu->ops.recv_buf(nu, (void *)data, flags, count);
|
||||
spin_unlock(&nu->rx_lock);
|
||||
|
||||
tty_unthrottle(tty);
|
||||
}
|
||||
|
||||
/* nci_uart_tty_ioctl()
|
||||
*
|
||||
* Process IOCTL system call for the tty device.
|
||||
*
|
||||
* Arguments:
|
||||
*
|
||||
* tty pointer to tty instance data
|
||||
* file pointer to open file object for device
|
||||
* cmd IOCTL command code
|
||||
* arg argument for IOCTL call (cmd dependent)
|
||||
*
|
||||
* Return Value: Command dependent
|
||||
*/
|
||||
static int nci_uart_tty_ioctl(struct tty_struct *tty, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct nci_uart *nu = (void *)tty->disc_data;
|
||||
int err = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case NCIUARTSETDRIVER:
|
||||
if (!nu)
|
||||
return nci_uart_set_driver(tty, (unsigned int)arg);
|
||||
else
|
||||
return -EBUSY;
|
||||
break;
|
||||
default:
|
||||
err = n_tty_ioctl_helper(tty, file, cmd, arg);
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* We don't provide read/write/poll interface for user space. */
|
||||
static ssize_t nci_uart_tty_read(struct tty_struct *tty, struct file *file,
|
||||
unsigned char __user *buf, size_t nr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t nci_uart_tty_write(struct tty_struct *tty, struct file *file,
|
||||
const unsigned char *data, size_t count)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int nci_uart_tty_poll(struct tty_struct *tty,
|
||||
struct file *filp, poll_table *wait)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nci_uart_send(struct nci_uart *nu, struct sk_buff *skb)
|
||||
{
|
||||
/* Queue TX packet */
|
||||
skb_queue_tail(&nu->tx_q, skb);
|
||||
|
||||
/* Try to start TX (if possible) */
|
||||
nci_uart_tx_wakeup(nu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -- Default recv_buf handler --
|
||||
*
|
||||
* This handler supposes that NCI frames are sent over UART link without any
|
||||
* framing. It reads NCI header, retrieve the packet size and once all packet
|
||||
* bytes are received it passes it to nci_uart driver for processing.
|
||||
*/
|
||||
static int nci_uart_default_recv_buf(struct nci_uart *nu, const u8 *data,
|
||||
char *flags, int count)
|
||||
{
|
||||
int chunk_len;
|
||||
|
||||
if (!nu->ndev) {
|
||||
nfc_err(nu->tty->dev,
|
||||
"receive data from tty but no NCI dev is attached yet, drop buffer\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Decode all incoming data in packets
|
||||
* and enqueue then for processing.
|
||||
*/
|
||||
while (count > 0) {
|
||||
/* If this is the first data of a packet, allocate a buffer */
|
||||
if (!nu->rx_skb) {
|
||||
nu->rx_packet_len = -1;
|
||||
nu->rx_skb = nci_skb_alloc(nu->ndev,
|
||||
NCI_MAX_PACKET_SIZE,
|
||||
GFP_KERNEL);
|
||||
if (!nu->rx_skb)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Eat byte after byte till full packet header is received */
|
||||
if (nu->rx_skb->len < NCI_CTRL_HDR_SIZE) {
|
||||
*skb_put(nu->rx_skb, 1) = *data++;
|
||||
--count;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Header was received but packet len was not read */
|
||||
if (nu->rx_packet_len < 0)
|
||||
nu->rx_packet_len = NCI_CTRL_HDR_SIZE +
|
||||
nci_plen(nu->rx_skb->data);
|
||||
|
||||
/* Compute how many bytes are missing and how many bytes can
|
||||
* be consumed.
|
||||
*/
|
||||
chunk_len = nu->rx_packet_len - nu->rx_skb->len;
|
||||
if (count < chunk_len)
|
||||
chunk_len = count;
|
||||
memcpy(skb_put(nu->rx_skb, chunk_len), data, chunk_len);
|
||||
data += chunk_len;
|
||||
count -= chunk_len;
|
||||
|
||||
/* Chcek if packet is fully received */
|
||||
if (nu->rx_packet_len == nu->rx_skb->len) {
|
||||
/* Pass RX packet to driver */
|
||||
if (nu->ops.recv(nu, nu->rx_skb) != 0)
|
||||
nfc_err(nu->tty->dev, "corrupted RX packet\n");
|
||||
/* Next packet will be a new one */
|
||||
nu->rx_skb = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -- Default recv handler -- */
|
||||
static int nci_uart_default_recv(struct nci_uart *nu, struct sk_buff *skb)
|
||||
{
|
||||
return nci_recv_frame(nu->ndev, skb);
|
||||
}
|
||||
|
||||
int nci_uart_register(struct nci_uart *nu)
|
||||
{
|
||||
if (!nu || !nu->ops.open ||
|
||||
!nu->ops.recv || !nu->ops.close)
|
||||
return -EINVAL;
|
||||
|
||||
/* Set the send callback */
|
||||
nu->ops.send = nci_uart_send;
|
||||
|
||||
/* Install default handlers if not overridden */
|
||||
if (!nu->ops.recv_buf)
|
||||
nu->ops.recv_buf = nci_uart_default_recv_buf;
|
||||
if (!nu->ops.recv)
|
||||
nu->ops.recv = nci_uart_default_recv;
|
||||
|
||||
/* Add this driver in the driver list */
|
||||
if (!nci_uart_drivers[nu->driver]) {
|
||||
pr_err("driver %d is already registered\n", nu->driver);
|
||||
return -EBUSY;
|
||||
}
|
||||
nci_uart_drivers[nu->driver] = nu;
|
||||
|
||||
pr_info("NCI uart driver '%s [%d]' registered\n", nu->name, nu->driver);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nci_uart_register);
|
||||
|
||||
void nci_uart_unregister(struct nci_uart *nu)
|
||||
{
|
||||
pr_info("NCI uart driver '%s [%d]' unregistered\n", nu->name,
|
||||
nu->driver);
|
||||
|
||||
/* Remove this driver from the driver list */
|
||||
nci_uart_drivers[nu->driver] = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nci_uart_unregister);
|
||||
|
||||
void nci_uart_set_config(struct nci_uart *nu, int baudrate, int flow_ctrl)
|
||||
{
|
||||
struct ktermios new_termios;
|
||||
|
||||
if (!nu->tty)
|
||||
return;
|
||||
|
||||
down_read(&nu->tty->termios_rwsem);
|
||||
new_termios = nu->tty->termios;
|
||||
up_read(&nu->tty->termios_rwsem);
|
||||
tty_termios_encode_baud_rate(&new_termios, baudrate, baudrate);
|
||||
|
||||
if (flow_ctrl)
|
||||
new_termios.c_cflag |= CRTSCTS;
|
||||
else
|
||||
new_termios.c_cflag &= ~CRTSCTS;
|
||||
|
||||
tty_set_termios(nu->tty, &new_termios);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nci_uart_set_config);
|
||||
|
||||
static struct tty_ldisc_ops nci_uart_ldisc = {
|
||||
.magic = TTY_LDISC_MAGIC,
|
||||
.owner = THIS_MODULE,
|
||||
.name = "n_nci",
|
||||
.open = nci_uart_tty_open,
|
||||
.close = nci_uart_tty_close,
|
||||
.read = nci_uart_tty_read,
|
||||
.write = nci_uart_tty_write,
|
||||
.poll = nci_uart_tty_poll,
|
||||
.receive_buf = nci_uart_tty_receive,
|
||||
.write_wakeup = nci_uart_tty_wakeup,
|
||||
.ioctl = nci_uart_tty_ioctl,
|
||||
};
|
||||
|
||||
static int __init nci_uart_init(void)
|
||||
{
|
||||
memset(nci_uart_drivers, 0, sizeof(nci_uart_drivers));
|
||||
return tty_register_ldisc(N_NCI, &nci_uart_ldisc);
|
||||
}
|
||||
|
||||
static void __exit nci_uart_exit(void)
|
||||
{
|
||||
tty_unregister_ldisc(N_NCI);
|
||||
}
|
||||
|
||||
module_init(nci_uart_init);
|
||||
module_exit(nci_uart_exit);
|
||||
|
||||
MODULE_AUTHOR("Marvell International Ltd.");
|
||||
MODULE_DESCRIPTION("NFC NCI UART driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS_LDISC(N_NCI);
|
@ -5,6 +5,12 @@
|
||||
* Lauro Ramos Venancio <lauro.venancio@openbossa.org>
|
||||
* Aloisio Almeida Jr <aloisio.almeida@openbossa.org>
|
||||
*
|
||||
* Vendor commands implementation based on net/wireless/nl80211.c
|
||||
* which is:
|
||||
*
|
||||
* Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
|
||||
* Copyright 2013-2014 Intel Mobile Communications GmbH
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
@ -1489,6 +1495,50 @@ static int nfc_genl_se_io(struct sk_buff *skb, struct genl_info *info)
|
||||
return nfc_se_io(dev, se_idx, apdu, apdu_len, se_io_cb, ctx);
|
||||
}
|
||||
|
||||
static int nfc_genl_vendor_cmd(struct sk_buff *skb,
|
||||
struct genl_info *info)
|
||||
{
|
||||
struct nfc_dev *dev;
|
||||
struct nfc_vendor_cmd *cmd;
|
||||
u32 dev_idx, vid, subcmd;
|
||||
u8 *data;
|
||||
size_t data_len;
|
||||
int i;
|
||||
|
||||
if (!info->attrs[NFC_ATTR_DEVICE_INDEX] ||
|
||||
!info->attrs[NFC_ATTR_VENDOR_ID] ||
|
||||
!info->attrs[NFC_ATTR_VENDOR_SUBCMD])
|
||||
return -EINVAL;
|
||||
|
||||
dev_idx = nla_get_u32(info->attrs[NFC_ATTR_DEVICE_INDEX]);
|
||||
vid = nla_get_u32(info->attrs[NFC_ATTR_VENDOR_ID]);
|
||||
subcmd = nla_get_u32(info->attrs[NFC_ATTR_VENDOR_SUBCMD]);
|
||||
|
||||
dev = nfc_get_device(dev_idx);
|
||||
if (!dev || !dev->vendor_cmds || !dev->n_vendor_cmds)
|
||||
return -ENODEV;
|
||||
|
||||
data = nla_data(info->attrs[NFC_ATTR_VENDOR_DATA]);
|
||||
if (data) {
|
||||
data_len = nla_len(info->attrs[NFC_ATTR_VENDOR_DATA]);
|
||||
if (data_len == 0)
|
||||
return -EINVAL;
|
||||
} else {
|
||||
data_len = 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < dev->n_vendor_cmds; i++) {
|
||||
cmd = &dev->vendor_cmds[i];
|
||||
|
||||
if (cmd->vendor_id != vid || cmd->subcmd != subcmd)
|
||||
continue;
|
||||
|
||||
return cmd->doit(dev, data, data_len);
|
||||
}
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static const struct genl_ops nfc_genl_ops[] = {
|
||||
{
|
||||
.cmd = NFC_CMD_GET_DEVICE,
|
||||
@ -1579,6 +1629,11 @@ static const struct genl_ops nfc_genl_ops[] = {
|
||||
.doit = nfc_genl_activate_target,
|
||||
.policy = nfc_genl_policy,
|
||||
},
|
||||
{
|
||||
.cmd = NFC_CMD_VENDOR,
|
||||
.doit = nfc_genl_vendor_cmd,
|
||||
.policy = nfc_genl_policy,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user