NFC 4.3 pull request
This is the NFC pull request for 4.3. With this one we have: - A new driver for Samsung's S3FWRN5 NFC chipset. In order to properly support this driver, a few NCI core routines needed to be exported. Future drivers like Intel's Fields Peak will benefit from this. - SPI support as a physical transport for STM st21nfcb. - An additional netlink API for sending replies back to userspace from vendor commands. - 2 small fixes for TI's trf7970a - A few st-nci fixes. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJV1l7oAAoJEIqAPN1PVmxK7DwP+QF3A6wCBzaNQKcla6LOl+Ru lGquPpFyihlDT/916IP7MnMNZYOP3ENdGll5lKts2yKxuty327Bb2UWNkaFP63Ei +zZwhoZXwh6dbK35kwnd87Cwgn0E8vTF+zHhC2MmP8uGDIgOuevb//2GBDayG88T rw4QMZsunT4o9x/bNK1uTlYaKPDs5pxXYFYUPXOQ2F5GqpUVFag3pLbZcJhpSZg7 9Y1KNLxwi/1rwO90JTXH9CQ+oWgVcH86nIlzqGznxgNdOoCF/V/0hdGlTzj8sE6T A3a0Qy1gaWQw9+9QoqE7YWu6JfEIgEhRgFx8dm4SsmUbrnlmgYBavEFeG7++1AG5 QByWh/h2po0MysNRCfhey2EExlZgdvc1WyLQlS+0w+aWmM5MYq+J4lx+sqXdUDdu MZyNDytRfGgRimBPSxyjuHtzrBWR8MyenjsbpraNoVDlFD8wVnGa5OcAv2gi7dkD wloOSZj5jJq9WoMeGWEwp2TsQMySJDydBSTvgqovlk2K8gmeY69g2YUUmwR2Truf fulBsO8upet/v2cRbCepI2X4NvS37wZBRcJtPpGcCoXUagA1vWH8eBKfjoduGERt vc5c9DYjSA0VEJ3kzu3Atro/oNrZDDqvX1wEn+64fk1B8v53Lvf2X0ESQQUcfWpz k7hPYzOt2IOA7d41CY59 =KMsn -----END PGP SIGNATURE----- Merge tag 'nfc-next-4.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/sameo/nfc-next Samuel Ortiz says: ==================== NFC 4.3 pull request This is the NFC pull request for 4.3. With this one we have: - A new driver for Samsung's S3FWRN5 NFC chipset. In order to properly support this driver, a few NCI core routines needed to be exported. Future drivers like Intel's Fields Peak will benefit from this. - SPI support as a physical transport for STM st21nfcb. - An additional netlink API for sending replies back to userspace from vendor commands. - 2 small fixes for TI's trf7970a - A few st-nci fixes. ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Коммит
d9893d1351
|
@ -0,0 +1,27 @@
|
||||||
|
* Samsung S3FWRN5 NCI NFC Controller
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: Should be "samsung,s3fwrn5-i2c".
|
||||||
|
- reg: address on the bus
|
||||||
|
- interrupt-parent: phandle for the interrupt gpio controller
|
||||||
|
- interrupts: GPIO interrupt to which the chip is connected
|
||||||
|
- s3fwrn5,en-gpios: Output GPIO pin used for enabling/disabling the chip
|
||||||
|
- s3fwrn5,fw-gpios: Output GPIO pin used to enter firmware mode and
|
||||||
|
sleep/wakeup control
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
&hsi2c_4 {
|
||||||
|
status = "okay";
|
||||||
|
s3fwrn5@27 {
|
||||||
|
compatible = "samsung,s3fwrn5-i2c";
|
||||||
|
|
||||||
|
reg = <0x27>;
|
||||||
|
|
||||||
|
interrupt-parent = <&gpa1>;
|
||||||
|
interrupts = <3 0 0>;
|
||||||
|
|
||||||
|
s3fwrn5,en-gpios = <&gpf1 4 0>;
|
||||||
|
s3fwrn5,fw-gpios = <&gpj0 2 0>;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,31 @@
|
||||||
|
* STMicroelectronics SAS. ST NCI NFC Controller
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: Should be "st,st21nfcb-spi"
|
||||||
|
- spi-max-frequency: Maximum SPI frequency (<= 10000000).
|
||||||
|
- interrupt-parent: phandle for the interrupt gpio controller
|
||||||
|
- interrupts: GPIO interrupt to which the chip is connected
|
||||||
|
- reset-gpios: Output GPIO pin used to reset the ST21NFCB
|
||||||
|
|
||||||
|
Optional SoC Specific Properties:
|
||||||
|
- pinctrl-names: Contains only one value - "default".
|
||||||
|
- pintctrl-0: Specifies the pin control groups used for this controller.
|
||||||
|
|
||||||
|
Example (for ARM-based BeagleBoard xM with ST21NFCB on SPI4):
|
||||||
|
|
||||||
|
&mcspi4 {
|
||||||
|
|
||||||
|
status = "okay";
|
||||||
|
|
||||||
|
st21nfcb: st21nfcb@0 {
|
||||||
|
|
||||||
|
compatible = "st,st21nfcb-spi";
|
||||||
|
|
||||||
|
clock-frequency = <4000000>;
|
||||||
|
|
||||||
|
interrupt-parent = <&gpio5>;
|
||||||
|
interrupts = <2 IRQ_TYPE_EDGE_RISING>;
|
||||||
|
|
||||||
|
reset-gpios = <&gpio5 29 GPIO_ACTIVE_HIGH>;
|
||||||
|
};
|
||||||
|
};
|
|
@ -8881,6 +8881,12 @@ L: linux-media@vger.kernel.org
|
||||||
S: Supported
|
S: Supported
|
||||||
F: drivers/media/i2c/s5k5baf.c
|
F: drivers/media/i2c/s5k5baf.c
|
||||||
|
|
||||||
|
SAMSUNG S3FWRN5 NFC DRIVER
|
||||||
|
M: Robert Baldyga <r.baldyga@samsung.com>
|
||||||
|
L: linux-nfc@lists.01.org (moderated for non-subscribers)
|
||||||
|
S: Supported
|
||||||
|
F: drivers/nfc/s3fwrn5
|
||||||
|
|
||||||
SAMSUNG SOC CLOCK DRIVERS
|
SAMSUNG SOC CLOCK DRIVERS
|
||||||
M: Sylwester Nawrocki <s.nawrocki@samsung.com>
|
M: Sylwester Nawrocki <s.nawrocki@samsung.com>
|
||||||
M: Tomasz Figa <tomasz.figa@gmail.com>
|
M: Tomasz Figa <tomasz.figa@gmail.com>
|
||||||
|
|
|
@ -74,4 +74,5 @@ source "drivers/nfc/nfcmrvl/Kconfig"
|
||||||
source "drivers/nfc/st21nfca/Kconfig"
|
source "drivers/nfc/st21nfca/Kconfig"
|
||||||
source "drivers/nfc/st-nci/Kconfig"
|
source "drivers/nfc/st-nci/Kconfig"
|
||||||
source "drivers/nfc/nxp-nci/Kconfig"
|
source "drivers/nfc/nxp-nci/Kconfig"
|
||||||
|
source "drivers/nfc/s3fwrn5/Kconfig"
|
||||||
endmenu
|
endmenu
|
||||||
|
|
|
@ -14,3 +14,4 @@ obj-$(CONFIG_NFC_TRF7970A) += trf7970a.o
|
||||||
obj-$(CONFIG_NFC_ST21NFCA) += st21nfca/
|
obj-$(CONFIG_NFC_ST21NFCA) += st21nfca/
|
||||||
obj-$(CONFIG_NFC_ST_NCI) += st-nci/
|
obj-$(CONFIG_NFC_ST_NCI) += st-nci/
|
||||||
obj-$(CONFIG_NFC_NXP_NCI) += nxp-nci/
|
obj-$(CONFIG_NFC_NXP_NCI) += nxp-nci/
|
||||||
|
obj-$(CONFIG_NFC_S3FWRN5) += s3fwrn5/
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
config NFC_S3FWRN5
|
||||||
|
tristate
|
||||||
|
---help---
|
||||||
|
Core driver for Samsung S3FWRN5 NFC chip. Contains core utilities
|
||||||
|
of chip. It's intended to be used by PHYs to avoid duplicating lots
|
||||||
|
of common code.
|
||||||
|
|
||||||
|
config NFC_S3FWRN5_I2C
|
||||||
|
tristate "Samsung S3FWRN5 I2C support"
|
||||||
|
depends on NFC_NCI && I2C
|
||||||
|
select NFC_S3FWRN5
|
||||||
|
default n
|
||||||
|
---help---
|
||||||
|
This module adds support for an I2C interface to the S3FWRN5 chip.
|
||||||
|
Select this if your platform is using the I2C bus.
|
||||||
|
|
||||||
|
To compile this driver as a module, choose m here. The module will
|
||||||
|
be called s3fwrn5_i2c.ko.
|
||||||
|
Say N if unsure.
|
|
@ -0,0 +1,11 @@
|
||||||
|
#
|
||||||
|
# Makefile for Samsung S3FWRN5 NFC driver
|
||||||
|
#
|
||||||
|
|
||||||
|
s3fwrn5-objs = core.o firmware.o nci.o
|
||||||
|
s3fwrn5_i2c-objs = i2c.o
|
||||||
|
|
||||||
|
obj-$(CONFIG_NFC_S3FWRN5) += s3fwrn5.o
|
||||||
|
obj-$(CONFIG_NFC_S3FWRN5_I2C) += s3fwrn5_i2c.o
|
||||||
|
|
||||||
|
ccflags-$(CONFIG_NFC_DEBUG) := -DDEBUG
|
|
@ -0,0 +1,219 @@
|
||||||
|
/*
|
||||||
|
* NCI based driver for Samsung S3FWRN5 NFC chip
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015 Samsung Electrnoics
|
||||||
|
* Robert Baldyga <r.baldyga@samsung.com>
|
||||||
|
*
|
||||||
|
* 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 or later, 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 <net/nfc/nci_core.h>
|
||||||
|
|
||||||
|
#include "s3fwrn5.h"
|
||||||
|
#include "firmware.h"
|
||||||
|
#include "nci.h"
|
||||||
|
|
||||||
|
#define S3FWRN5_NFC_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)
|
||||||
|
|
||||||
|
static int s3fwrn5_firmware_update(struct s3fwrn5_info *info)
|
||||||
|
{
|
||||||
|
bool need_update;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
s3fwrn5_fw_init(&info->fw_info, "sec_s3fwrn5_firmware.bin");
|
||||||
|
|
||||||
|
/* Update firmware */
|
||||||
|
|
||||||
|
s3fwrn5_set_wake(info, false);
|
||||||
|
s3fwrn5_set_mode(info, S3FWRN5_MODE_FW);
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_setup(&info->fw_info);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
need_update = s3fwrn5_fw_check_version(&info->fw_info,
|
||||||
|
info->ndev->manufact_specific_info);
|
||||||
|
if (!need_update)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
dev_info(&info->ndev->nfc_dev->dev, "Detected new firmware version\n");
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_download(&info->fw_info);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
/* Update RF configuration */
|
||||||
|
|
||||||
|
s3fwrn5_set_mode(info, S3FWRN5_MODE_NCI);
|
||||||
|
|
||||||
|
s3fwrn5_set_wake(info, true);
|
||||||
|
ret = s3fwrn5_nci_rf_configure(info, "sec_s3fwrn5_rfreg.bin");
|
||||||
|
s3fwrn5_set_wake(info, false);
|
||||||
|
|
||||||
|
out:
|
||||||
|
s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD);
|
||||||
|
s3fwrn5_fw_cleanup(&info->fw_info);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int s3fwrn5_nci_open(struct nci_dev *ndev)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
|
||||||
|
|
||||||
|
if (s3fwrn5_get_mode(info) != S3FWRN5_MODE_COLD)
|
||||||
|
return -EBUSY;
|
||||||
|
|
||||||
|
s3fwrn5_set_mode(info, S3FWRN5_MODE_NCI);
|
||||||
|
s3fwrn5_set_wake(info, true);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int s3fwrn5_nci_close(struct nci_dev *ndev)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
|
||||||
|
|
||||||
|
s3fwrn5_set_wake(info, false);
|
||||||
|
s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int s3fwrn5_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&info->mutex);
|
||||||
|
|
||||||
|
if (s3fwrn5_get_mode(info) != S3FWRN5_MODE_NCI) {
|
||||||
|
mutex_unlock(&info->mutex);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = s3fwrn5_write(info, skb);
|
||||||
|
if (ret < 0)
|
||||||
|
kfree_skb(skb);
|
||||||
|
|
||||||
|
mutex_unlock(&info->mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int s3fwrn5_nci_post_setup(struct nci_dev *ndev)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = s3fwrn5_firmware_update(info);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
/* NCI core reset */
|
||||||
|
|
||||||
|
s3fwrn5_set_mode(info, S3FWRN5_MODE_NCI);
|
||||||
|
s3fwrn5_set_wake(info, true);
|
||||||
|
|
||||||
|
ret = nci_core_reset(info->ndev);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
ret = nci_core_init(info->ndev);
|
||||||
|
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct nci_ops s3fwrn5_nci_ops = {
|
||||||
|
.open = s3fwrn5_nci_open,
|
||||||
|
.close = s3fwrn5_nci_close,
|
||||||
|
.send = s3fwrn5_nci_send,
|
||||||
|
.post_setup = s3fwrn5_nci_post_setup,
|
||||||
|
};
|
||||||
|
|
||||||
|
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
|
||||||
|
struct s3fwrn5_phy_ops *phy_ops, unsigned int max_payload)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_info *info;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
info = devm_kzalloc(pdev, sizeof(*info), GFP_KERNEL);
|
||||||
|
if (!info)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
info->phy_id = phy_id;
|
||||||
|
info->pdev = pdev;
|
||||||
|
info->phy_ops = phy_ops;
|
||||||
|
info->max_payload = max_payload;
|
||||||
|
mutex_init(&info->mutex);
|
||||||
|
|
||||||
|
s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD);
|
||||||
|
|
||||||
|
s3fwrn5_nci_get_prop_ops(&s3fwrn5_nci_ops.prop_ops,
|
||||||
|
&s3fwrn5_nci_ops.n_prop_ops);
|
||||||
|
|
||||||
|
info->ndev = nci_allocate_device(&s3fwrn5_nci_ops,
|
||||||
|
S3FWRN5_NFC_PROTOCOLS, 0, 0);
|
||||||
|
if (!info->ndev)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
nci_set_parent_dev(info->ndev, pdev);
|
||||||
|
nci_set_drvdata(info->ndev, info);
|
||||||
|
|
||||||
|
ret = nci_register_device(info->ndev);
|
||||||
|
if (ret < 0) {
|
||||||
|
nci_free_device(info->ndev);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
info->fw_info.ndev = info->ndev;
|
||||||
|
|
||||||
|
*ndev = info->ndev;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(s3fwrn5_probe);
|
||||||
|
|
||||||
|
void s3fwrn5_remove(struct nci_dev *ndev)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
|
||||||
|
|
||||||
|
s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD);
|
||||||
|
|
||||||
|
nci_unregister_device(ndev);
|
||||||
|
nci_free_device(ndev);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(s3fwrn5_remove);
|
||||||
|
|
||||||
|
int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb,
|
||||||
|
enum s3fwrn5_mode mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case S3FWRN5_MODE_NCI:
|
||||||
|
return nci_recv_frame(ndev, skb);
|
||||||
|
case S3FWRN5_MODE_FW:
|
||||||
|
return s3fwrn5_fw_recv_frame(ndev, skb);
|
||||||
|
default:
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(s3fwrn5_recv_frame);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_DESCRIPTION("Samsung S3FWRN5 NFC driver");
|
||||||
|
MODULE_AUTHOR("Robert Baldyga <r.baldyga@samsung.com>");
|
|
@ -0,0 +1,511 @@
|
||||||
|
/*
|
||||||
|
* NCI based driver for Samsung S3FWRN5 NFC chip
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015 Samsung Electrnoics
|
||||||
|
* Robert Baldyga <r.baldyga@samsung.com>
|
||||||
|
*
|
||||||
|
* 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 or later, 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/completion.h>
|
||||||
|
#include <linux/firmware.h>
|
||||||
|
#include <linux/crypto.h>
|
||||||
|
#include <crypto/sha.h>
|
||||||
|
|
||||||
|
#include "s3fwrn5.h"
|
||||||
|
#include "firmware.h"
|
||||||
|
|
||||||
|
struct s3fwrn5_fw_version {
|
||||||
|
__u8 major;
|
||||||
|
__u8 build1;
|
||||||
|
__u8 build2;
|
||||||
|
__u8 target;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int s3fwrn5_fw_send_msg(struct s3fwrn5_fw_info *fw_info,
|
||||||
|
struct sk_buff *msg, struct sk_buff **rsp)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_info *info =
|
||||||
|
container_of(fw_info, struct s3fwrn5_info, fw_info);
|
||||||
|
long ret;
|
||||||
|
|
||||||
|
reinit_completion(&fw_info->completion);
|
||||||
|
|
||||||
|
ret = s3fwrn5_write(info, msg);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = wait_for_completion_interruptible_timeout(
|
||||||
|
&fw_info->completion, msecs_to_jiffies(1000));
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
else if (ret == 0)
|
||||||
|
return -ENXIO;
|
||||||
|
|
||||||
|
if (!fw_info->rsp)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
*rsp = fw_info->rsp;
|
||||||
|
fw_info->rsp = NULL;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int s3fwrn5_fw_prep_msg(struct s3fwrn5_fw_info *fw_info,
|
||||||
|
struct sk_buff **msg, u8 type, u8 code, const void *data, u16 len)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_fw_header hdr;
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
hdr.type = type | fw_info->parity;
|
||||||
|
fw_info->parity ^= 0x80;
|
||||||
|
hdr.code = code;
|
||||||
|
hdr.len = len;
|
||||||
|
|
||||||
|
skb = alloc_skb(S3FWRN5_FW_HDR_SIZE + len, GFP_KERNEL);
|
||||||
|
if (!skb)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
memcpy(skb_put(skb, S3FWRN5_FW_HDR_SIZE), &hdr, S3FWRN5_FW_HDR_SIZE);
|
||||||
|
if (len)
|
||||||
|
memcpy(skb_put(skb, len), data, len);
|
||||||
|
|
||||||
|
*msg = skb;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int s3fwrn5_fw_get_bootinfo(struct s3fwrn5_fw_info *fw_info,
|
||||||
|
struct s3fwrn5_fw_cmd_get_bootinfo_rsp *bootinfo)
|
||||||
|
{
|
||||||
|
struct sk_buff *msg, *rsp = NULL;
|
||||||
|
struct s3fwrn5_fw_header *hdr;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Send GET_BOOTINFO command */
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_CMD,
|
||||||
|
S3FWRN5_FW_CMD_GET_BOOTINFO, NULL, 0);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp);
|
||||||
|
kfree_skb(msg);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
hdr = (struct s3fwrn5_fw_header *) rsp->data;
|
||||||
|
if (hdr->code != S3FWRN5_FW_RET_SUCCESS) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(bootinfo, rsp->data + S3FWRN5_FW_HDR_SIZE, 10);
|
||||||
|
|
||||||
|
out:
|
||||||
|
kfree_skb(rsp);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int s3fwrn5_fw_enter_update_mode(struct s3fwrn5_fw_info *fw_info,
|
||||||
|
const void *hash_data, u16 hash_size,
|
||||||
|
const void *sig_data, u16 sig_size)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_fw_cmd_enter_updatemode args;
|
||||||
|
struct sk_buff *msg, *rsp = NULL;
|
||||||
|
struct s3fwrn5_fw_header *hdr;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Send ENTER_UPDATE_MODE command */
|
||||||
|
|
||||||
|
args.hashcode_size = hash_size;
|
||||||
|
args.signature_size = sig_size;
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_CMD,
|
||||||
|
S3FWRN5_FW_CMD_ENTER_UPDATE_MODE, &args, sizeof(args));
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp);
|
||||||
|
kfree_skb(msg);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
hdr = (struct s3fwrn5_fw_header *) rsp->data;
|
||||||
|
if (hdr->code != S3FWRN5_FW_RET_SUCCESS) {
|
||||||
|
ret = -EPROTO;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
kfree_skb(rsp);
|
||||||
|
|
||||||
|
/* Send hashcode data */
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_DATA, 0,
|
||||||
|
hash_data, hash_size);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp);
|
||||||
|
kfree_skb(msg);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
hdr = (struct s3fwrn5_fw_header *) rsp->data;
|
||||||
|
if (hdr->code != S3FWRN5_FW_RET_SUCCESS) {
|
||||||
|
ret = -EPROTO;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
kfree_skb(rsp);
|
||||||
|
|
||||||
|
/* Send signature data */
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_DATA, 0,
|
||||||
|
sig_data, sig_size);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp);
|
||||||
|
kfree_skb(msg);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
hdr = (struct s3fwrn5_fw_header *) rsp->data;
|
||||||
|
if (hdr->code != S3FWRN5_FW_RET_SUCCESS)
|
||||||
|
ret = -EPROTO;
|
||||||
|
|
||||||
|
out:
|
||||||
|
kfree_skb(rsp);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int s3fwrn5_fw_update_sector(struct s3fwrn5_fw_info *fw_info,
|
||||||
|
u32 base_addr, const void *data)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_fw_cmd_update_sector args;
|
||||||
|
struct sk_buff *msg, *rsp = NULL;
|
||||||
|
struct s3fwrn5_fw_header *hdr;
|
||||||
|
int ret, i;
|
||||||
|
|
||||||
|
/* Send UPDATE_SECTOR command */
|
||||||
|
|
||||||
|
args.base_address = base_addr;
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_CMD,
|
||||||
|
S3FWRN5_FW_CMD_UPDATE_SECTOR, &args, sizeof(args));
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp);
|
||||||
|
kfree_skb(msg);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
hdr = (struct s3fwrn5_fw_header *) rsp->data;
|
||||||
|
if (hdr->code != S3FWRN5_FW_RET_SUCCESS) {
|
||||||
|
ret = -EPROTO;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
kfree_skb(rsp);
|
||||||
|
|
||||||
|
/* Send data split into 256-byte packets */
|
||||||
|
|
||||||
|
for (i = 0; i < 16; ++i) {
|
||||||
|
ret = s3fwrn5_fw_prep_msg(fw_info, &msg,
|
||||||
|
S3FWRN5_FW_MSG_DATA, 0, data+256*i, 256);
|
||||||
|
if (ret < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp);
|
||||||
|
kfree_skb(msg);
|
||||||
|
if (ret < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
hdr = (struct s3fwrn5_fw_header *) rsp->data;
|
||||||
|
if (hdr->code != S3FWRN5_FW_RET_SUCCESS) {
|
||||||
|
ret = -EPROTO;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
kfree_skb(rsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
err:
|
||||||
|
kfree_skb(rsp);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int s3fwrn5_fw_complete_update_mode(struct s3fwrn5_fw_info *fw_info)
|
||||||
|
{
|
||||||
|
struct sk_buff *msg, *rsp = NULL;
|
||||||
|
struct s3fwrn5_fw_header *hdr;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Send COMPLETE_UPDATE_MODE command */
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_CMD,
|
||||||
|
S3FWRN5_FW_CMD_COMPLETE_UPDATE_MODE, NULL, 0);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp);
|
||||||
|
kfree_skb(msg);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
hdr = (struct s3fwrn5_fw_header *) rsp->data;
|
||||||
|
if (hdr->code != S3FWRN5_FW_RET_SUCCESS)
|
||||||
|
ret = -EPROTO;
|
||||||
|
|
||||||
|
kfree_skb(rsp);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Firmware header stucture:
|
||||||
|
*
|
||||||
|
* 0x00 - 0x0B : Date and time string (w/o NUL termination)
|
||||||
|
* 0x10 - 0x13 : Firmware version
|
||||||
|
* 0x14 - 0x17 : Signature address
|
||||||
|
* 0x18 - 0x1B : Signature size
|
||||||
|
* 0x1C - 0x1F : Firmware image address
|
||||||
|
* 0x20 - 0x23 : Firmware sectors count
|
||||||
|
* 0x24 - 0x27 : Custom signature address
|
||||||
|
* 0x28 - 0x2B : Custom signature size
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define S3FWRN5_FW_IMAGE_HEADER_SIZE 44
|
||||||
|
|
||||||
|
static int s3fwrn5_fw_request_firmware(struct s3fwrn5_fw_info *fw_info)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_fw_image *fw = &fw_info->fw;
|
||||||
|
u32 sig_off;
|
||||||
|
u32 image_off;
|
||||||
|
u32 custom_sig_off;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = request_firmware(&fw->fw, fw_info->fw_name,
|
||||||
|
&fw_info->ndev->nfc_dev->dev);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (fw->fw->size < S3FWRN5_FW_IMAGE_HEADER_SIZE)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
memcpy(fw->date, fw->fw->data + 0x00, 12);
|
||||||
|
fw->date[12] = '\0';
|
||||||
|
|
||||||
|
memcpy(&fw->version, fw->fw->data + 0x10, 4);
|
||||||
|
|
||||||
|
memcpy(&sig_off, fw->fw->data + 0x14, 4);
|
||||||
|
fw->sig = fw->fw->data + sig_off;
|
||||||
|
memcpy(&fw->sig_size, fw->fw->data + 0x18, 4);
|
||||||
|
|
||||||
|
memcpy(&image_off, fw->fw->data + 0x1C, 4);
|
||||||
|
fw->image = fw->fw->data + image_off;
|
||||||
|
memcpy(&fw->image_sectors, fw->fw->data + 0x20, 4);
|
||||||
|
|
||||||
|
memcpy(&custom_sig_off, fw->fw->data + 0x24, 4);
|
||||||
|
fw->custom_sig = fw->fw->data + custom_sig_off;
|
||||||
|
memcpy(&fw->custom_sig_size, fw->fw->data + 0x28, 4);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void s3fwrn5_fw_release_firmware(struct s3fwrn5_fw_info *fw_info)
|
||||||
|
{
|
||||||
|
release_firmware(fw_info->fw.fw);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int s3fwrn5_fw_get_base_addr(
|
||||||
|
struct s3fwrn5_fw_cmd_get_bootinfo_rsp *bootinfo, u32 *base_addr)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct {
|
||||||
|
u8 version[4];
|
||||||
|
u32 base_addr;
|
||||||
|
} match[] = {
|
||||||
|
{{0x05, 0x00, 0x00, 0x00}, 0x00005000},
|
||||||
|
{{0x05, 0x00, 0x00, 0x01}, 0x00003000},
|
||||||
|
{{0x05, 0x00, 0x00, 0x02}, 0x00003000},
|
||||||
|
{{0x05, 0x00, 0x00, 0x03}, 0x00003000},
|
||||||
|
{{0x05, 0x00, 0x00, 0x05}, 0x00003000}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(match); ++i)
|
||||||
|
if (bootinfo->hw_version[0] == match[i].version[0] &&
|
||||||
|
bootinfo->hw_version[1] == match[i].version[1] &&
|
||||||
|
bootinfo->hw_version[3] == match[i].version[3]) {
|
||||||
|
*base_addr = match[i].base_addr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
s3fwrn5_fw_is_custom(struct s3fwrn5_fw_cmd_get_bootinfo_rsp *bootinfo)
|
||||||
|
{
|
||||||
|
return !!bootinfo->hw_version[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
int s3fwrn5_fw_setup(struct s3fwrn5_fw_info *fw_info)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_fw_cmd_get_bootinfo_rsp bootinfo;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Get firmware data */
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_request_firmware(fw_info);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&fw_info->ndev->nfc_dev->dev,
|
||||||
|
"Failed to get fw file, ret=%02x\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get bootloader info */
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_get_bootinfo(fw_info, &bootinfo);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&fw_info->ndev->nfc_dev->dev,
|
||||||
|
"Failed to get bootinfo, ret=%02x\n", ret);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Match hardware version to obtain firmware base address */
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_get_base_addr(&bootinfo, &fw_info->base_addr);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&fw_info->ndev->nfc_dev->dev,
|
||||||
|
"Unknown hardware version\n");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
fw_info->sector_size = bootinfo.sector_size;
|
||||||
|
|
||||||
|
fw_info->sig_size = s3fwrn5_fw_is_custom(&bootinfo) ?
|
||||||
|
fw_info->fw.custom_sig_size : fw_info->fw.sig_size;
|
||||||
|
fw_info->sig = s3fwrn5_fw_is_custom(&bootinfo) ?
|
||||||
|
fw_info->fw.custom_sig : fw_info->fw.sig;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err:
|
||||||
|
s3fwrn5_fw_release_firmware(fw_info);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool s3fwrn5_fw_check_version(struct s3fwrn5_fw_info *fw_info, u32 version)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_fw_version *new = (void *) &fw_info->fw.version;
|
||||||
|
struct s3fwrn5_fw_version *old = (void *) &version;
|
||||||
|
|
||||||
|
if (new->major > old->major)
|
||||||
|
return true;
|
||||||
|
if (new->build1 > old->build1)
|
||||||
|
return true;
|
||||||
|
if (new->build2 > old->build2)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int s3fwrn5_fw_download(struct s3fwrn5_fw_info *fw_info)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_fw_image *fw = &fw_info->fw;
|
||||||
|
u8 hash_data[SHA1_DIGEST_SIZE];
|
||||||
|
struct scatterlist sg;
|
||||||
|
struct hash_desc desc;
|
||||||
|
u32 image_size, off;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
image_size = fw_info->sector_size * fw->image_sectors;
|
||||||
|
|
||||||
|
/* Compute SHA of firmware data */
|
||||||
|
|
||||||
|
sg_init_one(&sg, fw->image, image_size);
|
||||||
|
desc.tfm = crypto_alloc_hash("sha1", 0, CRYPTO_ALG_ASYNC);
|
||||||
|
crypto_hash_init(&desc);
|
||||||
|
crypto_hash_update(&desc, &sg, image_size);
|
||||||
|
crypto_hash_final(&desc, hash_data);
|
||||||
|
crypto_free_hash(desc.tfm);
|
||||||
|
|
||||||
|
/* Firmware update process */
|
||||||
|
|
||||||
|
dev_info(&fw_info->ndev->nfc_dev->dev,
|
||||||
|
"Firmware update: %s\n", fw_info->fw_name);
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_enter_update_mode(fw_info, hash_data,
|
||||||
|
SHA1_DIGEST_SIZE, fw_info->sig, fw_info->sig_size);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&fw_info->ndev->nfc_dev->dev,
|
||||||
|
"Unable to enter update mode\n");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (off = 0; off < image_size; off += fw_info->sector_size) {
|
||||||
|
ret = s3fwrn5_fw_update_sector(fw_info,
|
||||||
|
fw_info->base_addr + off, fw->image + off);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&fw_info->ndev->nfc_dev->dev,
|
||||||
|
"Firmware update error (code=%d)\n", ret);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = s3fwrn5_fw_complete_update_mode(fw_info);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&fw_info->ndev->nfc_dev->dev,
|
||||||
|
"Unable to complete update mode\n");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_info(&fw_info->ndev->nfc_dev->dev,
|
||||||
|
"Firmware update: success\n");
|
||||||
|
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void s3fwrn5_fw_init(struct s3fwrn5_fw_info *fw_info, const char *fw_name)
|
||||||
|
{
|
||||||
|
fw_info->parity = 0x00;
|
||||||
|
fw_info->rsp = NULL;
|
||||||
|
fw_info->fw.fw = NULL;
|
||||||
|
strcpy(fw_info->fw_name, fw_name);
|
||||||
|
init_completion(&fw_info->completion);
|
||||||
|
}
|
||||||
|
|
||||||
|
void s3fwrn5_fw_cleanup(struct s3fwrn5_fw_info *fw_info)
|
||||||
|
{
|
||||||
|
s3fwrn5_fw_release_firmware(fw_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
int s3fwrn5_fw_recv_frame(struct nci_dev *ndev, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
|
||||||
|
struct s3fwrn5_fw_info *fw_info = &info->fw_info;
|
||||||
|
|
||||||
|
BUG_ON(fw_info->rsp);
|
||||||
|
|
||||||
|
fw_info->rsp = skb;
|
||||||
|
|
||||||
|
complete(&fw_info->completion);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* NCI based driver for Samsung S3FWRN5 NFC chip
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015 Samsung Electrnoics
|
||||||
|
* Robert Baldyga <r.baldyga@samsung.com>
|
||||||
|
*
|
||||||
|
* 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 or later, 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_S3FWRN5_FIRMWARE_H_
|
||||||
|
#define __LOCAL_S3FWRN5_FIRMWARE_H_
|
||||||
|
|
||||||
|
/* FW Message Types */
|
||||||
|
#define S3FWRN5_FW_MSG_CMD 0x00
|
||||||
|
#define S3FWRN5_FW_MSG_RSP 0x01
|
||||||
|
#define S3FWRN5_FW_MSG_DATA 0x02
|
||||||
|
|
||||||
|
/* FW Return Codes */
|
||||||
|
#define S3FWRN5_FW_RET_SUCCESS 0x00
|
||||||
|
#define S3FWRN5_FW_RET_MESSAGE_TYPE_INVALID 0x01
|
||||||
|
#define S3FWRN5_FW_RET_COMMAND_INVALID 0x02
|
||||||
|
#define S3FWRN5_FW_RET_PAGE_DATA_OVERFLOW 0x03
|
||||||
|
#define S3FWRN5_FW_RET_SECT_DATA_OVERFLOW 0x04
|
||||||
|
#define S3FWRN5_FW_RET_AUTHENTICATION_FAIL 0x05
|
||||||
|
#define S3FWRN5_FW_RET_FLASH_OPERATION_FAIL 0x06
|
||||||
|
#define S3FWRN5_FW_RET_ADDRESS_OUT_OF_RANGE 0x07
|
||||||
|
#define S3FWRN5_FW_RET_PARAMETER_INVALID 0x08
|
||||||
|
|
||||||
|
/* ---- FW Packet structures ---- */
|
||||||
|
#define S3FWRN5_FW_HDR_SIZE 4
|
||||||
|
|
||||||
|
struct s3fwrn5_fw_header {
|
||||||
|
__u8 type;
|
||||||
|
__u8 code;
|
||||||
|
__u16 len;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define S3FWRN5_FW_CMD_RESET 0x00
|
||||||
|
|
||||||
|
#define S3FWRN5_FW_CMD_GET_BOOTINFO 0x01
|
||||||
|
|
||||||
|
struct s3fwrn5_fw_cmd_get_bootinfo_rsp {
|
||||||
|
__u8 hw_version[4];
|
||||||
|
__u16 sector_size;
|
||||||
|
__u16 page_size;
|
||||||
|
__u16 frame_max_size;
|
||||||
|
__u16 hw_buffer_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define S3FWRN5_FW_CMD_ENTER_UPDATE_MODE 0x02
|
||||||
|
|
||||||
|
struct s3fwrn5_fw_cmd_enter_updatemode {
|
||||||
|
__u16 hashcode_size;
|
||||||
|
__u16 signature_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define S3FWRN5_FW_CMD_UPDATE_SECTOR 0x04
|
||||||
|
|
||||||
|
struct s3fwrn5_fw_cmd_update_sector {
|
||||||
|
__u32 base_address;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define S3FWRN5_FW_CMD_COMPLETE_UPDATE_MODE 0x05
|
||||||
|
|
||||||
|
struct s3fwrn5_fw_image {
|
||||||
|
const struct firmware *fw;
|
||||||
|
|
||||||
|
char date[13];
|
||||||
|
u32 version;
|
||||||
|
const void *sig;
|
||||||
|
u32 sig_size;
|
||||||
|
const void *image;
|
||||||
|
u32 image_sectors;
|
||||||
|
const void *custom_sig;
|
||||||
|
u32 custom_sig_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct s3fwrn5_fw_info {
|
||||||
|
struct nci_dev *ndev;
|
||||||
|
struct s3fwrn5_fw_image fw;
|
||||||
|
char fw_name[NFC_FIRMWARE_NAME_MAXSIZE + 1];
|
||||||
|
|
||||||
|
const void *sig;
|
||||||
|
u32 sig_size;
|
||||||
|
u32 sector_size;
|
||||||
|
u32 base_addr;
|
||||||
|
|
||||||
|
struct completion completion;
|
||||||
|
struct sk_buff *rsp;
|
||||||
|
char parity;
|
||||||
|
};
|
||||||
|
|
||||||
|
void s3fwrn5_fw_init(struct s3fwrn5_fw_info *fw_info, const char *fw_name);
|
||||||
|
int s3fwrn5_fw_setup(struct s3fwrn5_fw_info *fw_info);
|
||||||
|
bool s3fwrn5_fw_check_version(struct s3fwrn5_fw_info *fw_info, u32 version);
|
||||||
|
int s3fwrn5_fw_download(struct s3fwrn5_fw_info *fw_info);
|
||||||
|
void s3fwrn5_fw_cleanup(struct s3fwrn5_fw_info *fw_info);
|
||||||
|
|
||||||
|
int s3fwrn5_fw_recv_frame(struct nci_dev *ndev, struct sk_buff *skb);
|
||||||
|
|
||||||
|
#endif /* __LOCAL_S3FWRN5_FIRMWARE_H_ */
|
|
@ -0,0 +1,306 @@
|
||||||
|
/*
|
||||||
|
* I2C Link Layer for Samsung S3FWRN5 NCI based Driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015 Samsung Electrnoics
|
||||||
|
* Robert Baldyga <r.baldyga@samsung.com>
|
||||||
|
*
|
||||||
|
* 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 or later, 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/i2c.h>
|
||||||
|
#include <linux/gpio.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/of_gpio.h>
|
||||||
|
#include <linux/of_irq.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
|
||||||
|
#include <net/nfc/nfc.h>
|
||||||
|
|
||||||
|
#include "s3fwrn5.h"
|
||||||
|
|
||||||
|
#define S3FWRN5_I2C_DRIVER_NAME "s3fwrn5_i2c"
|
||||||
|
|
||||||
|
#define S3FWRN5_I2C_MAX_PAYLOAD 32
|
||||||
|
#define S3FWRN5_EN_WAIT_TIME 150
|
||||||
|
|
||||||
|
struct s3fwrn5_i2c_phy {
|
||||||
|
struct i2c_client *i2c_dev;
|
||||||
|
struct nci_dev *ndev;
|
||||||
|
|
||||||
|
unsigned int gpio_en;
|
||||||
|
unsigned int gpio_fw_wake;
|
||||||
|
|
||||||
|
struct mutex mutex;
|
||||||
|
|
||||||
|
enum s3fwrn5_mode mode;
|
||||||
|
unsigned int irq_skip:1;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void s3fwrn5_i2c_set_wake(void *phy_id, bool wake)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_i2c_phy *phy = phy_id;
|
||||||
|
|
||||||
|
mutex_lock(&phy->mutex);
|
||||||
|
gpio_set_value(phy->gpio_fw_wake, wake);
|
||||||
|
msleep(S3FWRN5_EN_WAIT_TIME/2);
|
||||||
|
mutex_unlock(&phy->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void s3fwrn5_i2c_set_mode(void *phy_id, enum s3fwrn5_mode mode)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_i2c_phy *phy = phy_id;
|
||||||
|
|
||||||
|
mutex_lock(&phy->mutex);
|
||||||
|
|
||||||
|
if (phy->mode == mode)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
phy->mode = mode;
|
||||||
|
|
||||||
|
gpio_set_value(phy->gpio_en, 1);
|
||||||
|
gpio_set_value(phy->gpio_fw_wake, 0);
|
||||||
|
if (mode == S3FWRN5_MODE_FW)
|
||||||
|
gpio_set_value(phy->gpio_fw_wake, 1);
|
||||||
|
|
||||||
|
if (mode != S3FWRN5_MODE_COLD) {
|
||||||
|
msleep(S3FWRN5_EN_WAIT_TIME);
|
||||||
|
gpio_set_value(phy->gpio_en, 0);
|
||||||
|
msleep(S3FWRN5_EN_WAIT_TIME/2);
|
||||||
|
}
|
||||||
|
|
||||||
|
phy->irq_skip = true;
|
||||||
|
|
||||||
|
out:
|
||||||
|
mutex_unlock(&phy->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum s3fwrn5_mode s3fwrn5_i2c_get_mode(void *phy_id)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_i2c_phy *phy = phy_id;
|
||||||
|
enum s3fwrn5_mode mode;
|
||||||
|
|
||||||
|
mutex_lock(&phy->mutex);
|
||||||
|
|
||||||
|
mode = phy->mode;
|
||||||
|
|
||||||
|
mutex_unlock(&phy->mutex);
|
||||||
|
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int s3fwrn5_i2c_write(void *phy_id, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_i2c_phy *phy = phy_id;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&phy->mutex);
|
||||||
|
|
||||||
|
phy->irq_skip = false;
|
||||||
|
|
||||||
|
ret = i2c_master_send(phy->i2c_dev, skb->data, skb->len);
|
||||||
|
if (ret == -EREMOTEIO) {
|
||||||
|
/* Retry, chip was in standby */
|
||||||
|
usleep_range(110000, 120000);
|
||||||
|
ret = i2c_master_send(phy->i2c_dev, skb->data, skb->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_unlock(&phy->mutex);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (ret != skb->len)
|
||||||
|
return -EREMOTEIO;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct s3fwrn5_phy_ops i2c_phy_ops = {
|
||||||
|
.set_wake = s3fwrn5_i2c_set_wake,
|
||||||
|
.set_mode = s3fwrn5_i2c_set_mode,
|
||||||
|
.get_mode = s3fwrn5_i2c_get_mode,
|
||||||
|
.write = s3fwrn5_i2c_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int s3fwrn5_i2c_read(struct s3fwrn5_i2c_phy *phy)
|
||||||
|
{
|
||||||
|
struct sk_buff *skb;
|
||||||
|
size_t hdr_size;
|
||||||
|
size_t data_len;
|
||||||
|
char hdr[4];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
hdr_size = (phy->mode == S3FWRN5_MODE_NCI) ?
|
||||||
|
NCI_CTRL_HDR_SIZE : S3FWRN5_FW_HDR_SIZE;
|
||||||
|
ret = i2c_master_recv(phy->i2c_dev, hdr, hdr_size);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (ret < hdr_size)
|
||||||
|
return -EBADMSG;
|
||||||
|
|
||||||
|
data_len = (phy->mode == S3FWRN5_MODE_NCI) ?
|
||||||
|
((struct nci_ctrl_hdr *)hdr)->plen :
|
||||||
|
((struct s3fwrn5_fw_header *)hdr)->len;
|
||||||
|
|
||||||
|
skb = alloc_skb(hdr_size + data_len, GFP_KERNEL);
|
||||||
|
if (!skb)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
memcpy(skb_put(skb, hdr_size), hdr, hdr_size);
|
||||||
|
|
||||||
|
if (data_len == 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
ret = i2c_master_recv(phy->i2c_dev, skb_put(skb, data_len), data_len);
|
||||||
|
if (ret != data_len) {
|
||||||
|
kfree_skb(skb);
|
||||||
|
return -EBADMSG;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
return s3fwrn5_recv_frame(phy->ndev, skb, phy->mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
static irqreturn_t s3fwrn5_i2c_irq_thread_fn(int irq, void *phy_id)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_i2c_phy *phy = phy_id;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (!phy || !phy->ndev) {
|
||||||
|
WARN_ON_ONCE(1);
|
||||||
|
return IRQ_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&phy->mutex);
|
||||||
|
|
||||||
|
if (phy->irq_skip)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
switch (phy->mode) {
|
||||||
|
case S3FWRN5_MODE_NCI:
|
||||||
|
case S3FWRN5_MODE_FW:
|
||||||
|
ret = s3fwrn5_i2c_read(phy);
|
||||||
|
break;
|
||||||
|
case S3FWRN5_MODE_COLD:
|
||||||
|
ret = -EREMOTEIO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
mutex_unlock(&phy->mutex);
|
||||||
|
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int s3fwrn5_i2c_parse_dt(struct i2c_client *client)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_i2c_phy *phy = i2c_get_clientdata(client);
|
||||||
|
struct device_node *np = client->dev.of_node;
|
||||||
|
|
||||||
|
if (!np)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
phy->gpio_en = of_get_named_gpio(np, "s3fwrn5,en-gpios", 0);
|
||||||
|
if (!gpio_is_valid(phy->gpio_en))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
phy->gpio_fw_wake = of_get_named_gpio(np, "s3fwrn5,fw-gpios", 0);
|
||||||
|
if (!gpio_is_valid(phy->gpio_fw_wake))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int s3fwrn5_i2c_probe(struct i2c_client *client,
|
||||||
|
const struct i2c_device_id *id)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_i2c_phy *phy;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
phy = devm_kzalloc(&client->dev, sizeof(*phy), GFP_KERNEL);
|
||||||
|
if (!phy)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
mutex_init(&phy->mutex);
|
||||||
|
phy->mode = S3FWRN5_MODE_COLD;
|
||||||
|
phy->irq_skip = true;
|
||||||
|
|
||||||
|
phy->i2c_dev = client;
|
||||||
|
i2c_set_clientdata(client, phy);
|
||||||
|
|
||||||
|
ret = s3fwrn5_i2c_parse_dt(client);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = devm_gpio_request_one(&phy->i2c_dev->dev, phy->gpio_en,
|
||||||
|
GPIOF_OUT_INIT_HIGH, "s3fwrn5_en");
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = devm_gpio_request_one(&phy->i2c_dev->dev, phy->gpio_fw_wake,
|
||||||
|
GPIOF_OUT_INIT_LOW, "s3fwrn5_fw_wake");
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = s3fwrn5_probe(&phy->ndev, phy, &phy->i2c_dev->dev, &i2c_phy_ops,
|
||||||
|
S3FWRN5_I2C_MAX_PAYLOAD);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = request_threaded_irq(phy->i2c_dev->irq, NULL,
|
||||||
|
s3fwrn5_i2c_irq_thread_fn, IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
|
||||||
|
S3FWRN5_I2C_DRIVER_NAME, phy);
|
||||||
|
if (ret)
|
||||||
|
s3fwrn5_remove(phy->ndev);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int s3fwrn5_i2c_remove(struct i2c_client *client)
|
||||||
|
{
|
||||||
|
struct s3fwrn5_i2c_phy *phy = i2c_get_clientdata(client);
|
||||||
|
|
||||||
|
s3fwrn5_remove(phy->ndev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct i2c_device_id s3fwrn5_i2c_id_table[] = {
|
||||||
|
{S3FWRN5_I2C_DRIVER_NAME, 0},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table);
|
||||||
|
|
||||||
|
static const struct of_device_id of_s3fwrn5_i2c_match[] = {
|
||||||
|
{ .compatible = "samsung,s3fwrn5-i2c", },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match);
|
||||||
|
|
||||||
|
static struct i2c_driver s3fwrn5_i2c_driver = {
|
||||||
|
.driver = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = S3FWRN5_I2C_DRIVER_NAME,
|
||||||
|
.of_match_table = of_match_ptr(of_s3fwrn5_i2c_match),
|
||||||
|
},
|
||||||
|
.probe = s3fwrn5_i2c_probe,
|
||||||
|
.remove = s3fwrn5_i2c_remove,
|
||||||
|
.id_table = s3fwrn5_i2c_id_table,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_i2c_driver(s3fwrn5_i2c_driver);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_DESCRIPTION("I2C driver for Samsung S3FWRN5");
|
||||||
|
MODULE_AUTHOR("Robert Baldyga <r.baldyga@samsung.com>");
|
|
@ -0,0 +1,165 @@
|
||||||
|
/*
|
||||||
|
* NCI based driver for Samsung S3FWRN5 NFC chip
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015 Samsung Electrnoics
|
||||||
|
* Robert Baldyga <r.baldyga@samsung.com>
|
||||||
|
*
|
||||||
|
* 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 or later, 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/completion.h>
|
||||||
|
#include <linux/firmware.h>
|
||||||
|
|
||||||
|
#include "s3fwrn5.h"
|
||||||
|
#include "nci.h"
|
||||||
|
|
||||||
|
static int s3fwrn5_nci_prop_rsp(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 s3fwrn5_nci_prop_ops[] = {
|
||||||
|
{
|
||||||
|
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||||
|
NCI_PROP_AGAIN),
|
||||||
|
.rsp = s3fwrn5_nci_prop_rsp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||||
|
NCI_PROP_GET_RFREG),
|
||||||
|
.rsp = s3fwrn5_nci_prop_rsp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||||
|
NCI_PROP_SET_RFREG),
|
||||||
|
.rsp = s3fwrn5_nci_prop_rsp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||||
|
NCI_PROP_GET_RFREG_VER),
|
||||||
|
.rsp = s3fwrn5_nci_prop_rsp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||||
|
NCI_PROP_SET_RFREG_VER),
|
||||||
|
.rsp = s3fwrn5_nci_prop_rsp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||||
|
NCI_PROP_START_RFREG),
|
||||||
|
.rsp = s3fwrn5_nci_prop_rsp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||||
|
NCI_PROP_STOP_RFREG),
|
||||||
|
.rsp = s3fwrn5_nci_prop_rsp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||||
|
NCI_PROP_FW_CFG),
|
||||||
|
.rsp = s3fwrn5_nci_prop_rsp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||||
|
NCI_PROP_WR_RESET),
|
||||||
|
.rsp = s3fwrn5_nci_prop_rsp,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
void s3fwrn5_nci_get_prop_ops(struct nci_prop_ops **ops, size_t *n)
|
||||||
|
{
|
||||||
|
*ops = s3fwrn5_nci_prop_ops;
|
||||||
|
*n = ARRAY_SIZE(s3fwrn5_nci_prop_ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define S3FWRN5_RFREG_SECTION_SIZE 252
|
||||||
|
|
||||||
|
int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name)
|
||||||
|
{
|
||||||
|
const struct firmware *fw;
|
||||||
|
struct nci_prop_fw_cfg_cmd fw_cfg;
|
||||||
|
struct nci_prop_set_rfreg_cmd set_rfreg;
|
||||||
|
struct nci_prop_stop_rfreg_cmd stop_rfreg;
|
||||||
|
u32 checksum;
|
||||||
|
int i, len;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = request_firmware(&fw, fw_name, &info->ndev->nfc_dev->dev);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Compute rfreg checksum */
|
||||||
|
|
||||||
|
checksum = 0;
|
||||||
|
for (i = 0; i < fw->size; i += 4)
|
||||||
|
checksum += *((u32 *)(fw->data+i));
|
||||||
|
|
||||||
|
/* Set default clock configuration for external crystal */
|
||||||
|
|
||||||
|
fw_cfg.clk_type = 0x01;
|
||||||
|
fw_cfg.clk_speed = 0xff;
|
||||||
|
fw_cfg.clk_req = 0xff;
|
||||||
|
ret = nci_prop_cmd(info->ndev, NCI_PROP_FW_CFG,
|
||||||
|
sizeof(fw_cfg), (__u8 *)&fw_cfg);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
/* Start rfreg configuration */
|
||||||
|
|
||||||
|
dev_info(&info->ndev->nfc_dev->dev,
|
||||||
|
"rfreg configuration update: %s\n", fw_name);
|
||||||
|
|
||||||
|
ret = nci_prop_cmd(info->ndev, NCI_PROP_START_RFREG, 0, NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&info->ndev->nfc_dev->dev,
|
||||||
|
"Unable to start rfreg update\n");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update rfreg */
|
||||||
|
|
||||||
|
set_rfreg.index = 0;
|
||||||
|
for (i = 0; i < fw->size; i += S3FWRN5_RFREG_SECTION_SIZE) {
|
||||||
|
len = (fw->size - i < S3FWRN5_RFREG_SECTION_SIZE) ?
|
||||||
|
(fw->size - i) : S3FWRN5_RFREG_SECTION_SIZE;
|
||||||
|
memcpy(set_rfreg.data, fw->data+i, len);
|
||||||
|
ret = nci_prop_cmd(info->ndev, NCI_PROP_SET_RFREG,
|
||||||
|
len+1, (__u8 *)&set_rfreg);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&info->ndev->nfc_dev->dev,
|
||||||
|
"rfreg update error (code=%d)\n", ret);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
set_rfreg.index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Finish rfreg configuration */
|
||||||
|
|
||||||
|
stop_rfreg.checksum = checksum & 0xffff;
|
||||||
|
ret = nci_prop_cmd(info->ndev, NCI_PROP_STOP_RFREG,
|
||||||
|
sizeof(stop_rfreg), (__u8 *)&stop_rfreg);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&info->ndev->nfc_dev->dev,
|
||||||
|
"Unable to stop rfreg update\n");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_info(&info->ndev->nfc_dev->dev,
|
||||||
|
"rfreg configuration update: success\n");
|
||||||
|
out:
|
||||||
|
release_firmware(fw);
|
||||||
|
return ret;
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* NCI based driver for Samsung S3FWRN5 NFC chip
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015 Samsung Electrnoics
|
||||||
|
* Robert Baldyga <r.baldyga@samsung.com>
|
||||||
|
*
|
||||||
|
* 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 or later, 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_S3FWRN5_NCI_H_
|
||||||
|
#define __LOCAL_S3FWRN5_NCI_H_
|
||||||
|
|
||||||
|
#include "s3fwrn5.h"
|
||||||
|
|
||||||
|
#define NCI_PROP_AGAIN 0x01
|
||||||
|
|
||||||
|
#define NCI_PROP_GET_RFREG 0x21
|
||||||
|
#define NCI_PROP_SET_RFREG 0x22
|
||||||
|
|
||||||
|
struct nci_prop_set_rfreg_cmd {
|
||||||
|
__u8 index;
|
||||||
|
__u8 data[252];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct nci_prop_set_rfreg_rsp {
|
||||||
|
__u8 status;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NCI_PROP_GET_RFREG_VER 0x24
|
||||||
|
|
||||||
|
struct nci_prop_get_rfreg_ver_rsp {
|
||||||
|
__u8 status;
|
||||||
|
__u8 data[8];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NCI_PROP_SET_RFREG_VER 0x25
|
||||||
|
|
||||||
|
struct nci_prop_set_rfreg_ver_cmd {
|
||||||
|
__u8 data[8];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct nci_prop_set_rfreg_ver_rsp {
|
||||||
|
__u8 status;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NCI_PROP_START_RFREG 0x26
|
||||||
|
|
||||||
|
struct nci_prop_start_rfreg_rsp {
|
||||||
|
__u8 status;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NCI_PROP_STOP_RFREG 0x27
|
||||||
|
|
||||||
|
struct nci_prop_stop_rfreg_cmd {
|
||||||
|
__u16 checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct nci_prop_stop_rfreg_rsp {
|
||||||
|
__u8 status;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NCI_PROP_FW_CFG 0x28
|
||||||
|
|
||||||
|
struct nci_prop_fw_cfg_cmd {
|
||||||
|
__u8 clk_type;
|
||||||
|
__u8 clk_speed;
|
||||||
|
__u8 clk_req;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct nci_prop_fw_cfg_rsp {
|
||||||
|
__u8 status;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NCI_PROP_WR_RESET 0x2f
|
||||||
|
|
||||||
|
void s3fwrn5_nci_get_prop_ops(struct nci_prop_ops **ops, size_t *n);
|
||||||
|
int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name);
|
||||||
|
|
||||||
|
#endif /* __LOCAL_S3FWRN5_NCI_H_ */
|
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* NCI based driver for Samsung S3FWRN5 NFC chip
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015 Samsung Electrnoics
|
||||||
|
* Robert Baldyga <r.baldyga@samsung.com>
|
||||||
|
*
|
||||||
|
* 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 or later, 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_S3FWRN5_H_
|
||||||
|
#define __LOCAL_S3FWRN5_H_
|
||||||
|
|
||||||
|
#include <linux/nfc.h>
|
||||||
|
|
||||||
|
#include <net/nfc/nci_core.h>
|
||||||
|
|
||||||
|
#include "firmware.h"
|
||||||
|
|
||||||
|
enum s3fwrn5_mode {
|
||||||
|
S3FWRN5_MODE_COLD,
|
||||||
|
S3FWRN5_MODE_NCI,
|
||||||
|
S3FWRN5_MODE_FW,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct s3fwrn5_phy_ops {
|
||||||
|
void (*set_wake)(void *id, bool sleep);
|
||||||
|
void (*set_mode)(void *id, enum s3fwrn5_mode);
|
||||||
|
enum s3fwrn5_mode (*get_mode)(void *id);
|
||||||
|
int (*write)(void *id, struct sk_buff *skb);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct s3fwrn5_info {
|
||||||
|
struct nci_dev *ndev;
|
||||||
|
void *phy_id;
|
||||||
|
struct device *pdev;
|
||||||
|
|
||||||
|
struct s3fwrn5_phy_ops *phy_ops;
|
||||||
|
unsigned int max_payload;
|
||||||
|
|
||||||
|
struct s3fwrn5_fw_info fw_info;
|
||||||
|
|
||||||
|
struct mutex mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline int s3fwrn5_set_mode(struct s3fwrn5_info *info,
|
||||||
|
enum s3fwrn5_mode mode)
|
||||||
|
{
|
||||||
|
if (!info->phy_ops->set_mode)
|
||||||
|
return -ENOTSUPP;
|
||||||
|
|
||||||
|
info->phy_ops->set_mode(info->phy_id, mode);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline enum s3fwrn5_mode s3fwrn5_get_mode(struct s3fwrn5_info *info)
|
||||||
|
{
|
||||||
|
if (!info->phy_ops->get_mode)
|
||||||
|
return -ENOTSUPP;
|
||||||
|
|
||||||
|
return info->phy_ops->get_mode(info->phy_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int s3fwrn5_set_wake(struct s3fwrn5_info *info, bool wake)
|
||||||
|
{
|
||||||
|
if (!info->phy_ops->set_wake)
|
||||||
|
return -ENOTSUPP;
|
||||||
|
|
||||||
|
info->phy_ops->set_wake(info->phy_id, wake);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int s3fwrn5_write(struct s3fwrn5_info *info, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
if (!info->phy_ops->write)
|
||||||
|
return -ENOTSUPP;
|
||||||
|
|
||||||
|
return info->phy_ops->write(info->phy_id, skb);
|
||||||
|
}
|
||||||
|
|
||||||
|
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
|
||||||
|
struct s3fwrn5_phy_ops *phy_ops, unsigned int max_payload);
|
||||||
|
void s3fwrn5_remove(struct nci_dev *ndev);
|
||||||
|
|
||||||
|
int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb,
|
||||||
|
enum s3fwrn5_mode mode);
|
||||||
|
|
||||||
|
#endif /* __LOCAL_S3FWRN5_H_ */
|
|
@ -21,3 +21,14 @@ config NFC_ST_NCI_I2C
|
||||||
|
|
||||||
If you choose to build a module, it'll be called st-nci_i2c.
|
If you choose to build a module, it'll be called st-nci_i2c.
|
||||||
Say N if unsure.
|
Say N if unsure.
|
||||||
|
|
||||||
|
config NFC_ST_NCI_SPI
|
||||||
|
tristate "NFC ST NCI spi support"
|
||||||
|
depends on NFC_ST_NCI && SPI
|
||||||
|
---help---
|
||||||
|
This module adds support for an SPI interface to the
|
||||||
|
STMicroelectronics NFC NCI chips familly.
|
||||||
|
Select this if your platform is using the spi bus.
|
||||||
|
|
||||||
|
If you choose to build a module, it'll be called st-nci_spi.
|
||||||
|
Say N if unsure.
|
||||||
|
|
|
@ -7,3 +7,6 @@ obj-$(CONFIG_NFC_ST_NCI) += st-nci.o
|
||||||
|
|
||||||
st-nci_i2c-objs = i2c.o
|
st-nci_i2c-objs = i2c.o
|
||||||
obj-$(CONFIG_NFC_ST_NCI_I2C) += st-nci_i2c.o
|
obj-$(CONFIG_NFC_ST_NCI_I2C) += st-nci_i2c.o
|
||||||
|
|
||||||
|
st-nci_spi-objs = spi.o
|
||||||
|
obj-$(CONFIG_NFC_ST_NCI_SPI) += st-nci_spi.o
|
||||||
|
|
|
@ -25,15 +25,15 @@
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/nfc.h>
|
#include <linux/nfc.h>
|
||||||
#include <linux/platform_data/st_nci.h>
|
#include <linux/platform_data/st-nci.h>
|
||||||
|
|
||||||
#include "ndlc.h"
|
#include "ndlc.h"
|
||||||
|
|
||||||
#define DRIVER_DESC "NCI NFC driver for ST21NFCB"
|
#define DRIVER_DESC "NCI NFC driver for ST_NCI"
|
||||||
|
|
||||||
/* ndlc header */
|
/* ndlc header */
|
||||||
#define ST21NFCB_FRAME_HEADROOM 1
|
#define ST_NCI_FRAME_HEADROOM 1
|
||||||
#define ST21NFCB_FRAME_TAILROOM 0
|
#define ST_NCI_FRAME_TAILROOM 0
|
||||||
|
|
||||||
#define ST_NCI_I2C_MIN_SIZE 4 /* PCB(1) + NCI Packet header(3) */
|
#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 ST_NCI_I2C_MAX_SIZE 250 /* req 4.2.1 */
|
||||||
|
@ -118,15 +118,10 @@ static int st_nci_i2c_write(void *phy_id, struct sk_buff *skb)
|
||||||
/*
|
/*
|
||||||
* Reads an ndlc frame and returns it in a newly allocated sk_buff.
|
* Reads an ndlc frame and returns it in a newly allocated sk_buff.
|
||||||
* returns:
|
* returns:
|
||||||
* frame size : if received frame is complete (find ST21NFCB_SOF_EOF at
|
* 0 : if received frame is complete
|
||||||
* end of read)
|
|
||||||
* -EAGAIN : if received frame is incomplete (not find ST21NFCB_SOF_EOF
|
|
||||||
* at end of read)
|
|
||||||
* -EREMOTEIO : i2c read error (fatal)
|
* -EREMOTEIO : i2c read error (fatal)
|
||||||
* -EBADMSG : frame was incorrect and discarded
|
* -EBADMSG : frame was incorrect and discarded
|
||||||
* (value returned from st_nci_i2c_repack)
|
* -ENOMEM : cannot allocate skb, frame dropped
|
||||||
* -EIO : if no ST21NFCB_SOF_EOF is found after reaching
|
|
||||||
* the read length end sequence
|
|
||||||
*/
|
*/
|
||||||
static int st_nci_i2c_read(struct st_nci_i2c_phy *phy,
|
static int st_nci_i2c_read(struct st_nci_i2c_phy *phy,
|
||||||
struct sk_buff **skb)
|
struct sk_buff **skb)
|
||||||
|
@ -179,7 +174,7 @@ static int st_nci_i2c_read(struct st_nci_i2c_phy *phy,
|
||||||
/*
|
/*
|
||||||
* Reads an ndlc frame from the chip.
|
* Reads an ndlc frame from the chip.
|
||||||
*
|
*
|
||||||
* On ST21NFCB, IRQ goes in idle state when read starts.
|
* On ST_NCI, IRQ goes in idle state when read starts.
|
||||||
*/
|
*/
|
||||||
static irqreturn_t st_nci_irq_thread_fn(int irq, void *phy_id)
|
static irqreturn_t st_nci_irq_thread_fn(int irq, void *phy_id)
|
||||||
{
|
{
|
||||||
|
@ -325,12 +320,12 @@ static int st_nci_i2c_probe(struct i2c_client *client,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
nfc_err(&client->dev,
|
nfc_err(&client->dev,
|
||||||
"st21nfcb platform resources not available\n");
|
"st_nci platform resources not available\n");
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
||||||
r = ndlc_probe(phy, &i2c_phy_ops, &client->dev,
|
r = ndlc_probe(phy, &i2c_phy_ops, &client->dev,
|
||||||
ST21NFCB_FRAME_HEADROOM, ST21NFCB_FRAME_TAILROOM,
|
ST_NCI_FRAME_HEADROOM, ST_NCI_FRAME_TAILROOM,
|
||||||
&phy->ndlc);
|
&phy->ndlc);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
nfc_err(&client->dev, "Unable to register ndlc layer\n");
|
nfc_err(&client->dev, "Unable to register ndlc layer\n");
|
||||||
|
|
|
@ -171,6 +171,8 @@ static void llt_ndlc_rcv_queue(struct llt_ndlc *ndlc)
|
||||||
if ((pcb & PCB_TYPE_MASK) == PCB_TYPE_SUPERVISOR) {
|
if ((pcb & PCB_TYPE_MASK) == PCB_TYPE_SUPERVISOR) {
|
||||||
switch (pcb & PCB_SYNC_MASK) {
|
switch (pcb & PCB_SYNC_MASK) {
|
||||||
case PCB_SYNC_ACK:
|
case PCB_SYNC_ACK:
|
||||||
|
skb = skb_dequeue(&ndlc->ack_pending_q);
|
||||||
|
kfree_skb(skb);
|
||||||
del_timer_sync(&ndlc->t1_timer);
|
del_timer_sync(&ndlc->t1_timer);
|
||||||
del_timer_sync(&ndlc->t2_timer);
|
del_timer_sync(&ndlc->t2_timer);
|
||||||
ndlc->t2_active = false;
|
ndlc->t2_active = false;
|
||||||
|
@ -192,12 +194,13 @@ static void llt_ndlc_rcv_queue(struct llt_ndlc *ndlc)
|
||||||
msecs_to_jiffies(NDLC_TIMER_T1_WAIT));
|
msecs_to_jiffies(NDLC_TIMER_T1_WAIT));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
pr_err("UNKNOWN Packet Control Byte=%d\n", pcb);
|
|
||||||
kfree_skb(skb);
|
kfree_skb(skb);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else if ((pcb & PCB_TYPE_MASK) == PCB_TYPE_DATAFRAME) {
|
||||||
nci_recv_frame(ndlc->ndev, skb);
|
nci_recv_frame(ndlc->ndev, skb);
|
||||||
|
} else {
|
||||||
|
kfree_skb(skb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,392 @@
|
||||||
|
/*
|
||||||
|
* SPI Link Layer for ST NCI 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,
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/spi/spi.h>
|
||||||
|
#include <linux/gpio.h>
|
||||||
|
#include <linux/of_irq.h>
|
||||||
|
#include <linux/of_gpio.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/nfc.h>
|
||||||
|
#include <linux/platform_data/st-nci.h>
|
||||||
|
|
||||||
|
#include "ndlc.h"
|
||||||
|
|
||||||
|
#define DRIVER_DESC "NCI NFC driver for ST_NCI"
|
||||||
|
|
||||||
|
/* ndlc header */
|
||||||
|
#define ST_NCI_FRAME_HEADROOM 1
|
||||||
|
#define ST_NCI_FRAME_TAILROOM 0
|
||||||
|
|
||||||
|
#define ST_NCI_SPI_MIN_SIZE 4 /* PCB(1) + NCI Packet header(3) */
|
||||||
|
#define ST_NCI_SPI_MAX_SIZE 250 /* req 4.2.1 */
|
||||||
|
|
||||||
|
#define ST_NCI_SPI_DRIVER_NAME "st_nci_spi"
|
||||||
|
|
||||||
|
static struct spi_device_id st_nci_spi_id_table[] = {
|
||||||
|
{ST_NCI_SPI_DRIVER_NAME, 0},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(spi, st_nci_spi_id_table);
|
||||||
|
|
||||||
|
struct st_nci_spi_phy {
|
||||||
|
struct spi_device *spi_dev;
|
||||||
|
struct llt_ndlc *ndlc;
|
||||||
|
|
||||||
|
unsigned int gpio_reset;
|
||||||
|
unsigned int irq_polarity;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SPI_DUMP_SKB(info, skb) \
|
||||||
|
do { \
|
||||||
|
pr_debug("%s:\n", info); \
|
||||||
|
print_hex_dump(KERN_DEBUG, "spi: ", DUMP_PREFIX_OFFSET, \
|
||||||
|
16, 1, (skb)->data, (skb)->len, 0); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static int st_nci_spi_enable(void *phy_id)
|
||||||
|
{
|
||||||
|
struct st_nci_spi_phy *phy = phy_id;
|
||||||
|
|
||||||
|
gpio_set_value(phy->gpio_reset, 0);
|
||||||
|
usleep_range(10000, 15000);
|
||||||
|
gpio_set_value(phy->gpio_reset, 1);
|
||||||
|
usleep_range(80000, 85000);
|
||||||
|
|
||||||
|
if (phy->ndlc->powered == 0)
|
||||||
|
enable_irq(phy->spi_dev->irq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void st_nci_spi_disable(void *phy_id)
|
||||||
|
{
|
||||||
|
struct st_nci_spi_phy *phy = phy_id;
|
||||||
|
|
||||||
|
disable_irq_nosync(phy->spi_dev->irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Writing a frame must not return the number of written bytes.
|
||||||
|
* It must return either zero for success, or <0 for error.
|
||||||
|
* In addition, it must not alter the skb
|
||||||
|
*/
|
||||||
|
static int st_nci_spi_write(void *phy_id, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
int r;
|
||||||
|
struct st_nci_spi_phy *phy = phy_id;
|
||||||
|
struct spi_device *dev = phy->spi_dev;
|
||||||
|
struct sk_buff *skb_rx;
|
||||||
|
u8 buf[ST_NCI_SPI_MAX_SIZE];
|
||||||
|
struct spi_transfer spi_xfer = {
|
||||||
|
.tx_buf = skb->data,
|
||||||
|
.rx_buf = buf,
|
||||||
|
.len = skb->len,
|
||||||
|
};
|
||||||
|
|
||||||
|
SPI_DUMP_SKB("st_nci_spi_write", skb);
|
||||||
|
|
||||||
|
if (phy->ndlc->hard_fault != 0)
|
||||||
|
return phy->ndlc->hard_fault;
|
||||||
|
|
||||||
|
r = spi_sync_transfer(dev, &spi_xfer, 1);
|
||||||
|
/*
|
||||||
|
* We may have received some valuable data on miso line.
|
||||||
|
* Send them back in the ndlc state machine.
|
||||||
|
*/
|
||||||
|
if (!r) {
|
||||||
|
skb_rx = alloc_skb(skb->len, GFP_KERNEL);
|
||||||
|
if (!skb_rx) {
|
||||||
|
r = -ENOMEM;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
skb_put(skb_rx, skb->len);
|
||||||
|
memcpy(skb_rx->data, buf, skb->len);
|
||||||
|
ndlc_recv(phy->ndlc, skb_rx);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reads an ndlc frame and returns it in a newly allocated sk_buff.
|
||||||
|
* returns:
|
||||||
|
* 0 : if received frame is complete
|
||||||
|
* -EREMOTEIO : i2c read error (fatal)
|
||||||
|
* -EBADMSG : frame was incorrect and discarded
|
||||||
|
* -ENOMEM : cannot allocate skb, frame dropped
|
||||||
|
*/
|
||||||
|
static int st_nci_spi_read(struct st_nci_spi_phy *phy,
|
||||||
|
struct sk_buff **skb)
|
||||||
|
{
|
||||||
|
int r;
|
||||||
|
u8 len;
|
||||||
|
u8 buf[ST_NCI_SPI_MAX_SIZE];
|
||||||
|
struct spi_device *dev = phy->spi_dev;
|
||||||
|
struct spi_transfer spi_xfer = {
|
||||||
|
.rx_buf = buf,
|
||||||
|
.len = ST_NCI_SPI_MIN_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
r = spi_sync_transfer(dev, &spi_xfer, 1);
|
||||||
|
if (r < 0)
|
||||||
|
return -EREMOTEIO;
|
||||||
|
|
||||||
|
len = be16_to_cpu(*(__be16 *) (buf + 2));
|
||||||
|
if (len > ST_NCI_SPI_MAX_SIZE) {
|
||||||
|
nfc_err(&dev->dev, "invalid frame len\n");
|
||||||
|
phy->ndlc->hard_fault = 1;
|
||||||
|
return -EBADMSG;
|
||||||
|
}
|
||||||
|
|
||||||
|
*skb = alloc_skb(ST_NCI_SPI_MIN_SIZE + len, GFP_KERNEL);
|
||||||
|
if (*skb == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
skb_reserve(*skb, ST_NCI_SPI_MIN_SIZE);
|
||||||
|
skb_put(*skb, ST_NCI_SPI_MIN_SIZE);
|
||||||
|
memcpy((*skb)->data, buf, ST_NCI_SPI_MIN_SIZE);
|
||||||
|
|
||||||
|
if (!len)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
spi_xfer.len = len;
|
||||||
|
r = spi_sync_transfer(dev, &spi_xfer, 1);
|
||||||
|
if (r < 0) {
|
||||||
|
kfree_skb(*skb);
|
||||||
|
return -EREMOTEIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
skb_put(*skb, len);
|
||||||
|
memcpy((*skb)->data + ST_NCI_SPI_MIN_SIZE, buf, len);
|
||||||
|
|
||||||
|
SPI_DUMP_SKB("spi frame read", *skb);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reads an ndlc frame from the chip.
|
||||||
|
*
|
||||||
|
* On ST21NFCB, IRQ goes in idle state when read starts.
|
||||||
|
*/
|
||||||
|
static irqreturn_t st_nci_irq_thread_fn(int irq, void *phy_id)
|
||||||
|
{
|
||||||
|
struct st_nci_spi_phy *phy = phy_id;
|
||||||
|
struct spi_device *dev;
|
||||||
|
struct sk_buff *skb = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (!phy || !phy->ndlc || irq != phy->spi_dev->irq) {
|
||||||
|
WARN_ON_ONCE(1);
|
||||||
|
return IRQ_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev = phy->spi_dev;
|
||||||
|
dev_dbg(&dev->dev, "IRQ\n");
|
||||||
|
|
||||||
|
if (phy->ndlc->hard_fault)
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
|
||||||
|
if (!phy->ndlc->powered) {
|
||||||
|
st_nci_spi_disable(phy);
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = st_nci_spi_read(phy, &skb);
|
||||||
|
if (r == -EREMOTEIO || r == -ENOMEM || r == -EBADMSG)
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
|
||||||
|
ndlc_recv(phy->ndlc, skb);
|
||||||
|
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct nfc_phy_ops spi_phy_ops = {
|
||||||
|
.write = st_nci_spi_write,
|
||||||
|
.enable = st_nci_spi_enable,
|
||||||
|
.disable = st_nci_spi_disable,
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef CONFIG_OF
|
||||||
|
static int st_nci_spi_of_request_resources(struct spi_device *dev)
|
||||||
|
{
|
||||||
|
struct st_nci_spi_phy *phy = spi_get_drvdata(dev);
|
||||||
|
struct device_node *pp;
|
||||||
|
int gpio;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
pp = dev->dev.of_node;
|
||||||
|
if (!pp)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
/* Get GPIO from device tree */
|
||||||
|
gpio = of_get_named_gpio(pp, "reset-gpios", 0);
|
||||||
|
if (gpio < 0) {
|
||||||
|
nfc_err(&dev->dev,
|
||||||
|
"Failed to retrieve reset-gpios from device tree\n");
|
||||||
|
return gpio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GPIO request and configuration */
|
||||||
|
r = devm_gpio_request_one(&dev->dev, gpio,
|
||||||
|
GPIOF_OUT_INIT_HIGH, "clf_reset");
|
||||||
|
if (r) {
|
||||||
|
nfc_err(&dev->dev, "Failed to request reset pin\n");
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
phy->gpio_reset = gpio;
|
||||||
|
|
||||||
|
phy->irq_polarity = irq_get_trigger_type(dev->irq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static int st_nci_spi_of_request_resources(struct spi_device *dev)
|
||||||
|
{
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int st_nci_spi_request_resources(struct spi_device *dev)
|
||||||
|
{
|
||||||
|
struct st_nci_nfc_platform_data *pdata;
|
||||||
|
struct st_nci_spi_phy *phy = spi_get_drvdata(dev);
|
||||||
|
int r;
|
||||||
|
|
||||||
|
pdata = dev->dev.platform_data;
|
||||||
|
if (pdata == NULL) {
|
||||||
|
nfc_err(&dev->dev, "No platform data\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* store for later use */
|
||||||
|
phy->gpio_reset = pdata->gpio_reset;
|
||||||
|
phy->irq_polarity = pdata->irq_polarity;
|
||||||
|
|
||||||
|
r = devm_gpio_request_one(&dev->dev,
|
||||||
|
phy->gpio_reset, GPIOF_OUT_INIT_HIGH, "clf_reset");
|
||||||
|
if (r) {
|
||||||
|
pr_err("%s : reset gpio_request failed\n", __FILE__);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int st_nci_spi_probe(struct spi_device *dev)
|
||||||
|
{
|
||||||
|
struct st_nci_spi_phy *phy;
|
||||||
|
struct st_nci_nfc_platform_data *pdata;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
dev_dbg(&dev->dev, "%s\n", __func__);
|
||||||
|
dev_dbg(&dev->dev, "IRQ: %d\n", dev->irq);
|
||||||
|
|
||||||
|
/* Check SPI platform functionnalities */
|
||||||
|
if (!dev) {
|
||||||
|
pr_debug("%s: dev is NULL. Device is not accessible.\n",
|
||||||
|
__func__);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
phy = devm_kzalloc(&dev->dev, sizeof(struct st_nci_spi_phy),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!phy)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
phy->spi_dev = dev;
|
||||||
|
|
||||||
|
spi_set_drvdata(dev, phy);
|
||||||
|
|
||||||
|
pdata = dev->dev.platform_data;
|
||||||
|
if (!pdata && dev->dev.of_node) {
|
||||||
|
r = st_nci_spi_of_request_resources(dev);
|
||||||
|
if (r) {
|
||||||
|
nfc_err(&dev->dev, "No platform data\n");
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
} else if (pdata) {
|
||||||
|
r = st_nci_spi_request_resources(dev);
|
||||||
|
if (r) {
|
||||||
|
nfc_err(&dev->dev,
|
||||||
|
"Cannot get platform resources\n");
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nfc_err(&dev->dev,
|
||||||
|
"st_nci platform resources not available\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = ndlc_probe(phy, &spi_phy_ops, &dev->dev,
|
||||||
|
ST_NCI_FRAME_HEADROOM, ST_NCI_FRAME_TAILROOM,
|
||||||
|
&phy->ndlc);
|
||||||
|
if (r < 0) {
|
||||||
|
nfc_err(&dev->dev, "Unable to register ndlc layer\n");
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = devm_request_threaded_irq(&dev->dev, dev->irq, NULL,
|
||||||
|
st_nci_irq_thread_fn,
|
||||||
|
phy->irq_polarity | IRQF_ONESHOT,
|
||||||
|
ST_NCI_SPI_DRIVER_NAME, phy);
|
||||||
|
if (r < 0)
|
||||||
|
nfc_err(&dev->dev, "Unable to register IRQ handler\n");
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int st_nci_spi_remove(struct spi_device *dev)
|
||||||
|
{
|
||||||
|
struct st_nci_spi_phy *phy = spi_get_drvdata(dev);
|
||||||
|
|
||||||
|
dev_dbg(&dev->dev, "%s\n", __func__);
|
||||||
|
|
||||||
|
ndlc_remove(phy->ndlc);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_OF
|
||||||
|
static const struct of_device_id of_st_nci_spi_match[] = {
|
||||||
|
{ .compatible = "st,st21nfcb-spi", },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, of_st_nci_spi_match);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static struct spi_driver st_nci_spi_driver = {
|
||||||
|
.driver = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = ST_NCI_SPI_DRIVER_NAME,
|
||||||
|
.of_match_table = of_match_ptr(of_st_nci_spi_match),
|
||||||
|
},
|
||||||
|
.probe = st_nci_spi_probe,
|
||||||
|
.id_table = st_nci_spi_id_table,
|
||||||
|
.remove = st_nci_spi_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_spi_driver(st_nci_spi_driver);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
|
@ -189,14 +189,14 @@ int st_nci_hci_load_session(struct nci_dev *ndev)
|
||||||
ST_NCI_DEVICE_MGNT_GATE,
|
ST_NCI_DEVICE_MGNT_GATE,
|
||||||
ST_NCI_DEVICE_MGNT_PIPE);
|
ST_NCI_DEVICE_MGNT_PIPE);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
goto free_info;
|
return r;
|
||||||
|
|
||||||
/* Get pipe list */
|
/* Get pipe list */
|
||||||
r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
|
r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE,
|
||||||
ST_NCI_DM_GETINFO, pipe_list, sizeof(pipe_list),
|
ST_NCI_DM_GETINFO, pipe_list, sizeof(pipe_list),
|
||||||
&skb_pipe_list);
|
&skb_pipe_list);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
goto free_info;
|
return r;
|
||||||
|
|
||||||
/* Complete the existing gate_pipe table */
|
/* Complete the existing gate_pipe table */
|
||||||
for (i = 0; i < skb_pipe_list->len; i++) {
|
for (i = 0; i < skb_pipe_list->len; i++) {
|
||||||
|
@ -222,6 +222,7 @@ int st_nci_hci_load_session(struct nci_dev *ndev)
|
||||||
dm_pipe_info->src_host_id != ST_NCI_ESE_HOST_ID) {
|
dm_pipe_info->src_host_id != ST_NCI_ESE_HOST_ID) {
|
||||||
pr_err("Unexpected apdu_reader pipe on host %x\n",
|
pr_err("Unexpected apdu_reader pipe on host %x\n",
|
||||||
dm_pipe_info->src_host_id);
|
dm_pipe_info->src_host_id);
|
||||||
|
kfree_skb(skb_pipe_info);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,13 +242,12 @@ int st_nci_hci_load_session(struct nci_dev *ndev)
|
||||||
ndev->hci_dev->pipes[st_nci_gates[j].pipe].host =
|
ndev->hci_dev->pipes[st_nci_gates[j].pipe].host =
|
||||||
dm_pipe_info->src_host_id;
|
dm_pipe_info->src_host_id;
|
||||||
}
|
}
|
||||||
|
kfree_skb(skb_pipe_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(ndev->hci_dev->init_data.gates, st_nci_gates,
|
memcpy(ndev->hci_dev->init_data.gates, st_nci_gates,
|
||||||
sizeof(st_nci_gates));
|
sizeof(st_nci_gates));
|
||||||
|
|
||||||
free_info:
|
|
||||||
kfree_skb(skb_pipe_info);
|
|
||||||
kfree_skb(skb_pipe_list);
|
kfree_skb(skb_pipe_list);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,14 +148,14 @@ static int st21nfca_hci_load_session(struct nfc_hci_dev *hdev)
|
||||||
ST21NFCA_DEVICE_MGNT_GATE,
|
ST21NFCA_DEVICE_MGNT_GATE,
|
||||||
ST21NFCA_DEVICE_MGNT_PIPE);
|
ST21NFCA_DEVICE_MGNT_PIPE);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
goto free_info;
|
return r;
|
||||||
|
|
||||||
/* Get pipe list */
|
/* Get pipe list */
|
||||||
r = nfc_hci_send_cmd(hdev, ST21NFCA_DEVICE_MGNT_GATE,
|
r = nfc_hci_send_cmd(hdev, ST21NFCA_DEVICE_MGNT_GATE,
|
||||||
ST21NFCA_DM_GETINFO, pipe_list, sizeof(pipe_list),
|
ST21NFCA_DM_GETINFO, pipe_list, sizeof(pipe_list),
|
||||||
&skb_pipe_list);
|
&skb_pipe_list);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
goto free_info;
|
return r;
|
||||||
|
|
||||||
/* Complete the existing gate_pipe table */
|
/* Complete the existing gate_pipe table */
|
||||||
for (i = 0; i < skb_pipe_list->len; i++) {
|
for (i = 0; i < skb_pipe_list->len; i++) {
|
||||||
|
@ -181,6 +181,7 @@ static int st21nfca_hci_load_session(struct nfc_hci_dev *hdev)
|
||||||
info->src_host_id != ST21NFCA_ESE_HOST_ID) {
|
info->src_host_id != ST21NFCA_ESE_HOST_ID) {
|
||||||
pr_err("Unexpected apdu_reader pipe on host %x\n",
|
pr_err("Unexpected apdu_reader pipe on host %x\n",
|
||||||
info->src_host_id);
|
info->src_host_id);
|
||||||
|
kfree_skb(skb_pipe_info);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,6 +201,7 @@ static int st21nfca_hci_load_session(struct nfc_hci_dev *hdev)
|
||||||
hdev->pipes[st21nfca_gates[j].pipe].dest_host =
|
hdev->pipes[st21nfca_gates[j].pipe].dest_host =
|
||||||
info->src_host_id;
|
info->src_host_id;
|
||||||
}
|
}
|
||||||
|
kfree_skb(skb_pipe_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -214,13 +216,12 @@ static int st21nfca_hci_load_session(struct nfc_hci_dev *hdev)
|
||||||
st21nfca_gates[i].gate,
|
st21nfca_gates[i].gate,
|
||||||
st21nfca_gates[i].pipe);
|
st21nfca_gates[i].pipe);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
goto free_info;
|
goto free_list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(hdev->init_data.gates, st21nfca_gates, sizeof(st21nfca_gates));
|
memcpy(hdev->init_data.gates, st21nfca_gates, sizeof(st21nfca_gates));
|
||||||
free_info:
|
free_list:
|
||||||
kfree_skb(skb_pipe_info);
|
|
||||||
kfree_skb(skb_pipe_list);
|
kfree_skb(skb_pipe_list);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
|
@ -336,7 +336,7 @@
|
||||||
|
|
||||||
#define TRF7970A_NFC_TARGET_LEVEL_RFDET(v) ((v) & 0x07)
|
#define TRF7970A_NFC_TARGET_LEVEL_RFDET(v) ((v) & 0x07)
|
||||||
#define TRF7970A_NFC_TARGET_LEVEL_HI_RF BIT(3)
|
#define TRF7970A_NFC_TARGET_LEVEL_HI_RF BIT(3)
|
||||||
#define TRF7970A_NFC_TARGET_LEVEL_SDD_EN BIT(3)
|
#define TRF7970A_NFC_TARGET_LEVEL_SDD_EN BIT(5)
|
||||||
#define TRF7970A_NFC_TARGET_LEVEL_LD_S_4BYTES (0x0 << 6)
|
#define TRF7970A_NFC_TARGET_LEVEL_LD_S_4BYTES (0x0 << 6)
|
||||||
#define TRF7970A_NFC_TARGET_LEVEL_LD_S_7BYTES (0x1 << 6)
|
#define TRF7970A_NFC_TARGET_LEVEL_LD_S_7BYTES (0x1 << 6)
|
||||||
#define TRF7970A_NFC_TARGET_LEVEL_LD_S_10BYTES (0x2 << 6)
|
#define TRF7970A_NFC_TARGET_LEVEL_LD_S_10BYTES (0x2 << 6)
|
||||||
|
@ -629,7 +629,9 @@ static void trf7970a_send_upstream(struct trf7970a *trf)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trf->adjust_resp_len) {
|
if (trf->adjust_resp_len) {
|
||||||
skb_trim(trf->rx_skb, trf->rx_skb->len - 1);
|
if (trf->rx_skb)
|
||||||
|
skb_trim(trf->rx_skb, trf->rx_skb->len - 1);
|
||||||
|
|
||||||
trf->adjust_resp_len = false;
|
trf->adjust_resp_len = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* 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_ */
|
|
|
@ -79,6 +79,7 @@ struct nci_ops {
|
||||||
int (*close)(struct nci_dev *ndev);
|
int (*close)(struct nci_dev *ndev);
|
||||||
int (*send)(struct nci_dev *ndev, struct sk_buff *skb);
|
int (*send)(struct nci_dev *ndev, struct sk_buff *skb);
|
||||||
int (*setup)(struct nci_dev *ndev);
|
int (*setup)(struct nci_dev *ndev);
|
||||||
|
int (*post_setup)(struct nci_dev *ndev);
|
||||||
int (*fw_download)(struct nci_dev *ndev, const char *firmware_name);
|
int (*fw_download)(struct nci_dev *ndev, const char *firmware_name);
|
||||||
__u32 (*get_rfprotocol)(struct nci_dev *ndev, __u8 rf_protocol);
|
__u32 (*get_rfprotocol)(struct nci_dev *ndev, __u8 rf_protocol);
|
||||||
int (*discover_se)(struct nci_dev *ndev);
|
int (*discover_se)(struct nci_dev *ndev);
|
||||||
|
@ -277,6 +278,8 @@ int nci_request(struct nci_dev *ndev,
|
||||||
unsigned long opt),
|
unsigned long opt),
|
||||||
unsigned long opt, __u32 timeout);
|
unsigned long opt, __u32 timeout);
|
||||||
int nci_prop_cmd(struct nci_dev *ndev, __u8 oid, size_t len, __u8 *payload);
|
int nci_prop_cmd(struct nci_dev *ndev, __u8 oid, size_t len, __u8 *payload);
|
||||||
|
int nci_core_reset(struct nci_dev *ndev);
|
||||||
|
int nci_core_init(struct nci_dev *ndev);
|
||||||
|
|
||||||
int nci_recv_frame(struct nci_dev *ndev, struct sk_buff *skb);
|
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);
|
int nci_set_config(struct nci_dev *ndev, __u8 id, size_t len, __u8 *val);
|
||||||
|
|
|
@ -203,6 +203,7 @@ struct nfc_dev {
|
||||||
int n_vendor_cmds;
|
int n_vendor_cmds;
|
||||||
|
|
||||||
struct nfc_ops *ops;
|
struct nfc_ops *ops;
|
||||||
|
struct genl_info *cur_cmd_info;
|
||||||
};
|
};
|
||||||
#define to_nfc_dev(_dev) container_of(_dev, struct nfc_dev, dev)
|
#define to_nfc_dev(_dev) container_of(_dev, struct nfc_dev, dev)
|
||||||
|
|
||||||
|
@ -318,4 +319,44 @@ static inline int nfc_set_vendor_cmds(struct nfc_dev *dev,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct sk_buff *__nfc_alloc_vendor_cmd_reply_skb(struct nfc_dev *dev,
|
||||||
|
enum nfc_attrs attr,
|
||||||
|
u32 oui, u32 subcmd,
|
||||||
|
int approxlen);
|
||||||
|
int nfc_vendor_cmd_reply(struct sk_buff *skb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nfc_vendor_cmd_alloc_reply_skb - allocate vendor command reply
|
||||||
|
* @dev: nfc device
|
||||||
|
* @oui: vendor oui
|
||||||
|
* @approxlen: an upper bound of the length of the data that will
|
||||||
|
* be put into the skb
|
||||||
|
*
|
||||||
|
* This function allocates and pre-fills an skb for a reply to
|
||||||
|
* a vendor command. Since it is intended for a reply, calling
|
||||||
|
* it outside of a vendor command's doit() operation is invalid.
|
||||||
|
*
|
||||||
|
* The returned skb is pre-filled with some identifying data in
|
||||||
|
* a way that any data that is put into the skb (with skb_put(),
|
||||||
|
* nla_put() or similar) will end up being within the
|
||||||
|
* %NFC_ATTR_VENDOR_DATA attribute, so all that needs to be done
|
||||||
|
* with the skb is adding data for the corresponding userspace tool
|
||||||
|
* which can then read that data out of the vendor data attribute.
|
||||||
|
* You must not modify the skb in any other way.
|
||||||
|
*
|
||||||
|
* When done, call nfc_vendor_cmd_reply() with the skb and return
|
||||||
|
* its error code as the result of the doit() operation.
|
||||||
|
*
|
||||||
|
* Return: An allocated and pre-filled skb. %NULL if any errors happen.
|
||||||
|
*/
|
||||||
|
static inline struct sk_buff *
|
||||||
|
nfc_vendor_cmd_alloc_reply_skb(struct nfc_dev *dev,
|
||||||
|
u32 oui, u32 subcmd, int approxlen)
|
||||||
|
{
|
||||||
|
return __nfc_alloc_vendor_cmd_reply_skb(dev,
|
||||||
|
NFC_ATTR_VENDOR_DATA,
|
||||||
|
oui,
|
||||||
|
subcmd, approxlen);
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* __NET_NFC_H */
|
#endif /* __NET_NFC_H */
|
||||||
|
|
|
@ -351,6 +351,20 @@ int nci_prop_cmd(struct nci_dev *ndev, __u8 oid, size_t len, __u8 *payload)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(nci_prop_cmd);
|
EXPORT_SYMBOL(nci_prop_cmd);
|
||||||
|
|
||||||
|
int nci_core_reset(struct nci_dev *ndev)
|
||||||
|
{
|
||||||
|
return __nci_request(ndev, nci_reset_req, 0,
|
||||||
|
msecs_to_jiffies(NCI_RESET_TIMEOUT));
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(nci_core_reset);
|
||||||
|
|
||||||
|
int nci_core_init(struct nci_dev *ndev)
|
||||||
|
{
|
||||||
|
return __nci_request(ndev, nci_init_req, 0,
|
||||||
|
msecs_to_jiffies(NCI_INIT_TIMEOUT));
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(nci_core_init);
|
||||||
|
|
||||||
static int nci_open_device(struct nci_dev *ndev)
|
static int nci_open_device(struct nci_dev *ndev)
|
||||||
{
|
{
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
@ -388,6 +402,10 @@ static int nci_open_device(struct nci_dev *ndev)
|
||||||
msecs_to_jiffies(NCI_INIT_TIMEOUT));
|
msecs_to_jiffies(NCI_INIT_TIMEOUT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ndev->ops->post_setup) {
|
||||||
|
rc = ndev->ops->post_setup(ndev);
|
||||||
|
}
|
||||||
|
|
||||||
if (!rc) {
|
if (!rc) {
|
||||||
rc = __nci_request(ndev, nci_init_complete_req, 0,
|
rc = __nci_request(ndev, nci_init_complete_req, 0,
|
||||||
msecs_to_jiffies(NCI_INIT_TIMEOUT));
|
msecs_to_jiffies(NCI_INIT_TIMEOUT));
|
||||||
|
|
|
@ -233,7 +233,7 @@ int nci_hci_send_cmd(struct nci_dev *ndev, u8 gate, u8 cmd,
|
||||||
r = nci_request(ndev, nci_hci_send_data_req, (unsigned long)&data,
|
r = nci_request(ndev, nci_hci_send_data_req, (unsigned long)&data,
|
||||||
msecs_to_jiffies(NCI_DATA_TIMEOUT));
|
msecs_to_jiffies(NCI_DATA_TIMEOUT));
|
||||||
|
|
||||||
if (r == NCI_STATUS_OK)
|
if (r == NCI_STATUS_OK && skb)
|
||||||
*skb = conn_info->rx_skb;
|
*skb = conn_info->rx_skb;
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
|
|
|
@ -63,6 +63,8 @@ static const struct nla_policy nfc_genl_policy[NFC_ATTR_MAX + 1] = {
|
||||||
[NFC_ATTR_FIRMWARE_NAME] = { .type = NLA_STRING,
|
[NFC_ATTR_FIRMWARE_NAME] = { .type = NLA_STRING,
|
||||||
.len = NFC_FIRMWARE_NAME_MAXSIZE },
|
.len = NFC_FIRMWARE_NAME_MAXSIZE },
|
||||||
[NFC_ATTR_SE_APDU] = { .type = NLA_BINARY },
|
[NFC_ATTR_SE_APDU] = { .type = NLA_BINARY },
|
||||||
|
[NFC_ATTR_VENDOR_DATA] = { .type = NLA_BINARY },
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct nla_policy nfc_sdp_genl_policy[NFC_SDP_ATTR_MAX + 1] = {
|
static const struct nla_policy nfc_sdp_genl_policy[NFC_SDP_ATTR_MAX + 1] = {
|
||||||
|
@ -1503,7 +1505,7 @@ static int nfc_genl_vendor_cmd(struct sk_buff *skb,
|
||||||
u32 dev_idx, vid, subcmd;
|
u32 dev_idx, vid, subcmd;
|
||||||
u8 *data;
|
u8 *data;
|
||||||
size_t data_len;
|
size_t data_len;
|
||||||
int i;
|
int i, err;
|
||||||
|
|
||||||
if (!info->attrs[NFC_ATTR_DEVICE_INDEX] ||
|
if (!info->attrs[NFC_ATTR_DEVICE_INDEX] ||
|
||||||
!info->attrs[NFC_ATTR_VENDOR_ID] ||
|
!info->attrs[NFC_ATTR_VENDOR_ID] ||
|
||||||
|
@ -1518,12 +1520,13 @@ static int nfc_genl_vendor_cmd(struct sk_buff *skb,
|
||||||
if (!dev || !dev->vendor_cmds || !dev->n_vendor_cmds)
|
if (!dev || !dev->vendor_cmds || !dev->n_vendor_cmds)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
data = nla_data(info->attrs[NFC_ATTR_VENDOR_DATA]);
|
if (info->attrs[NFC_ATTR_VENDOR_DATA]) {
|
||||||
if (data) {
|
data = nla_data(info->attrs[NFC_ATTR_VENDOR_DATA]);
|
||||||
data_len = nla_len(info->attrs[NFC_ATTR_VENDOR_DATA]);
|
data_len = nla_len(info->attrs[NFC_ATTR_VENDOR_DATA]);
|
||||||
if (data_len == 0)
|
if (data_len == 0)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
} else {
|
} else {
|
||||||
|
data = NULL;
|
||||||
data_len = 0;
|
data_len = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1533,12 +1536,92 @@ static int nfc_genl_vendor_cmd(struct sk_buff *skb,
|
||||||
if (cmd->vendor_id != vid || cmd->subcmd != subcmd)
|
if (cmd->vendor_id != vid || cmd->subcmd != subcmd)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
return cmd->doit(dev, data, data_len);
|
dev->cur_cmd_info = info;
|
||||||
|
err = cmd->doit(dev, data, data_len);
|
||||||
|
dev->cur_cmd_info = NULL;
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* message building helper */
|
||||||
|
static inline void *nfc_hdr_put(struct sk_buff *skb, u32 portid, u32 seq,
|
||||||
|
int flags, u8 cmd)
|
||||||
|
{
|
||||||
|
/* since there is no private header just add the generic one */
|
||||||
|
return genlmsg_put(skb, portid, seq, &nfc_genl_family, flags, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct sk_buff *
|
||||||
|
__nfc_alloc_vendor_cmd_skb(struct nfc_dev *dev, int approxlen,
|
||||||
|
u32 portid, u32 seq,
|
||||||
|
enum nfc_attrs attr,
|
||||||
|
u32 oui, u32 subcmd, gfp_t gfp)
|
||||||
|
{
|
||||||
|
struct sk_buff *skb;
|
||||||
|
void *hdr;
|
||||||
|
|
||||||
|
skb = nlmsg_new(approxlen + 100, gfp);
|
||||||
|
if (!skb)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
hdr = nfc_hdr_put(skb, portid, seq, 0, NFC_CMD_VENDOR);
|
||||||
|
if (!hdr) {
|
||||||
|
kfree_skb(skb);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nla_put_u32(skb, NFC_ATTR_DEVICE_INDEX, dev->idx))
|
||||||
|
goto nla_put_failure;
|
||||||
|
if (nla_put_u32(skb, NFC_ATTR_VENDOR_ID, oui))
|
||||||
|
goto nla_put_failure;
|
||||||
|
if (nla_put_u32(skb, NFC_ATTR_VENDOR_SUBCMD, subcmd))
|
||||||
|
goto nla_put_failure;
|
||||||
|
|
||||||
|
((void **)skb->cb)[0] = dev;
|
||||||
|
((void **)skb->cb)[1] = hdr;
|
||||||
|
|
||||||
|
return skb;
|
||||||
|
|
||||||
|
nla_put_failure:
|
||||||
|
kfree_skb(skb);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sk_buff *__nfc_alloc_vendor_cmd_reply_skb(struct nfc_dev *dev,
|
||||||
|
enum nfc_attrs attr,
|
||||||
|
u32 oui, u32 subcmd,
|
||||||
|
int approxlen)
|
||||||
|
{
|
||||||
|
if (WARN_ON(!dev->cur_cmd_info))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return __nfc_alloc_vendor_cmd_skb(dev, approxlen,
|
||||||
|
dev->cur_cmd_info->snd_portid,
|
||||||
|
dev->cur_cmd_info->snd_seq, attr,
|
||||||
|
oui, subcmd, GFP_KERNEL);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(__nfc_alloc_vendor_cmd_reply_skb);
|
||||||
|
|
||||||
|
int nfc_vendor_cmd_reply(struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct nfc_dev *dev = ((void **)skb->cb)[0];
|
||||||
|
void *hdr = ((void **)skb->cb)[1];
|
||||||
|
|
||||||
|
/* clear CB data for netlink core to own from now on */
|
||||||
|
memset(skb->cb, 0, sizeof(skb->cb));
|
||||||
|
|
||||||
|
if (WARN_ON(!dev->cur_cmd_info)) {
|
||||||
|
kfree_skb(skb);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
genlmsg_end(skb, hdr);
|
||||||
|
return genlmsg_reply(skb, dev->cur_cmd_info);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(nfc_vendor_cmd_reply);
|
||||||
|
|
||||||
static const struct genl_ops nfc_genl_ops[] = {
|
static const struct genl_ops nfc_genl_ops[] = {
|
||||||
{
|
{
|
||||||
.cmd = NFC_CMD_GET_DEVICE,
|
.cmd = NFC_CMD_GET_DEVICE,
|
||||||
|
|
Загрузка…
Ссылка в новой задаче