diff options
29 files changed, 2180 insertions, 61 deletions
diff --git a/Documentation/devicetree/bindings/net/nfc/s3fwrn5.txt b/Documentation/devicetree/bindings/net/nfc/s3fwrn5.txt new file mode 100644 index 000000000000..fb1e75facf1b --- /dev/null +++ b/Documentation/devicetree/bindings/net/nfc/s3fwrn5.txt @@ -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>; + }; +}; diff --git a/Documentation/devicetree/bindings/net/nfc/st-nci.txt b/Documentation/devicetree/bindings/net/nfc/st-nci-i2c.txt index d707588ed734..d707588ed734 100644 --- a/Documentation/devicetree/bindings/net/nfc/st-nci.txt +++ b/Documentation/devicetree/bindings/net/nfc/st-nci-i2c.txt diff --git a/Documentation/devicetree/bindings/net/nfc/st-nci-spi.txt b/Documentation/devicetree/bindings/net/nfc/st-nci-spi.txt new file mode 100644 index 000000000000..525681b6dc39 --- /dev/null +++ b/Documentation/devicetree/bindings/net/nfc/st-nci-spi.txt @@ -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>; + }; +}; diff --git a/MAINTAINERS b/MAINTAINERS index 7b1b552630b8..4e6dcb692d30 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8881,6 +8881,12 @@ L: linux-media@vger.kernel.org S: Supported 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 M: Sylwester Nawrocki <s.nawrocki@samsung.com> M: Tomasz Figa <tomasz.figa@gmail.com> diff --git a/drivers/nfc/Kconfig b/drivers/nfc/Kconfig index 722673cb785b..6639cd1cae36 100644 --- a/drivers/nfc/Kconfig +++ b/drivers/nfc/Kconfig @@ -74,4 +74,5 @@ source "drivers/nfc/nfcmrvl/Kconfig" source "drivers/nfc/st21nfca/Kconfig" source "drivers/nfc/st-nci/Kconfig" source "drivers/nfc/nxp-nci/Kconfig" +source "drivers/nfc/s3fwrn5/Kconfig" endmenu diff --git a/drivers/nfc/Makefile b/drivers/nfc/Makefile index 368b6dfe71b3..2757fe1b8aa5 100644 --- a/drivers/nfc/Makefile +++ b/drivers/nfc/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_NFC_TRF7970A) += trf7970a.o obj-$(CONFIG_NFC_ST21NFCA) += st21nfca/ obj-$(CONFIG_NFC_ST_NCI) += st-nci/ obj-$(CONFIG_NFC_NXP_NCI) += nxp-nci/ +obj-$(CONFIG_NFC_S3FWRN5) += s3fwrn5/ diff --git a/drivers/nfc/s3fwrn5/Kconfig b/drivers/nfc/s3fwrn5/Kconfig new file mode 100644 index 000000000000..7e3b255b3f99 --- /dev/null +++ b/drivers/nfc/s3fwrn5/Kconfig @@ -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. diff --git a/drivers/nfc/s3fwrn5/Makefile b/drivers/nfc/s3fwrn5/Makefile new file mode 100644 index 000000000000..3381c34faf62 --- /dev/null +++ b/drivers/nfc/s3fwrn5/Makefile @@ -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 diff --git a/drivers/nfc/s3fwrn5/core.c b/drivers/nfc/s3fwrn5/core.c new file mode 100644 index 000000000000..0d866ca295e3 --- /dev/null +++ b/drivers/nfc/s3fwrn5/core.c @@ -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>"); diff --git a/drivers/nfc/s3fwrn5/firmware.c b/drivers/nfc/s3fwrn5/firmware.c new file mode 100644 index 000000000000..64a90252c57f --- /dev/null +++ b/drivers/nfc/s3fwrn5/firmware.c @@ -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; +} diff --git a/drivers/nfc/s3fwrn5/firmware.h b/drivers/nfc/s3fwrn5/firmware.h new file mode 100644 index 000000000000..1ec0647ab917 --- /dev/null +++ b/drivers/nfc/s3fwrn5/firmware.h @@ -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_ */ diff --git a/drivers/nfc/s3fwrn5/i2c.c b/drivers/nfc/s3fwrn5/i2c.c new file mode 100644 index 000000000000..b4dd7dd47473 --- /dev/null +++ b/drivers/nfc/s3fwrn5/i2c.c @@ -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>"); diff --git a/drivers/nfc/s3fwrn5/nci.c b/drivers/nfc/s3fwrn5/nci.c new file mode 100644 index 000000000000..ace0071c5339 --- /dev/null +++ b/drivers/nfc/s3fwrn5/nci.c @@ -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; +} diff --git a/drivers/nfc/s3fwrn5/nci.h b/drivers/nfc/s3fwrn5/nci.h new file mode 100644 index 000000000000..0e68d439dde6 --- /dev/null +++ b/drivers/nfc/s3fwrn5/nci.h @@ -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_ */ diff --git a/drivers/nfc/s3fwrn5/s3fwrn5.h b/drivers/nfc/s3fwrn5/s3fwrn5.h new file mode 100644 index 000000000000..89210d4828b8 --- /dev/null +++ b/drivers/nfc/s3fwrn5/s3fwrn5.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_ */ diff --git a/drivers/nfc/st-nci/Kconfig b/drivers/nfc/st-nci/Kconfig index fc3904c946ee..e7c6db9c5860 100644 --- a/drivers/nfc/st-nci/Kconfig +++ b/drivers/nfc/st-nci/Kconfig @@ -21,3 +21,14 @@ config NFC_ST_NCI_I2C If you choose to build a module, it'll be called st-nci_i2c. 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. diff --git a/drivers/nfc/st-nci/Makefile b/drivers/nfc/st-nci/Makefile index 0df157df3a94..348ce76f2177 100644 --- a/drivers/nfc/st-nci/Makefile +++ b/drivers/nfc/st-nci/Makefile @@ -7,3 +7,6 @@ obj-$(CONFIG_NFC_ST_NCI) += st-nci.o st-nci_i2c-objs = 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 diff --git a/drivers/nfc/st-nci/i2c.c b/drivers/nfc/st-nci/i2c.c index 06175ce769bb..707ed2eb5936 100644 --- a/drivers/nfc/st-nci/i2c.c +++ b/drivers/nfc/st-nci/i2c.c @@ -25,15 +25,15 @@ #include <linux/interrupt.h> #include <linux/delay.h> #include <linux/nfc.h> -#include <linux/platform_data/st_nci.h> +#include <linux/platform_data/st-nci.h> #include "ndlc.h" -#define DRIVER_DESC "NCI NFC driver for ST21NFCB" +#define DRIVER_DESC "NCI NFC driver for ST_NCI" /* ndlc header */ -#define ST21NFCB_FRAME_HEADROOM 1 -#define ST21NFCB_FRAME_TAILROOM 0 +#define ST_NCI_FRAME_HEADROOM 1 +#define ST_NCI_FRAME_TAILROOM 0 #define ST_NCI_I2C_MIN_SIZE 4 /* PCB(1) + NCI Packet header(3) */ #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. * returns: - * frame size : if received frame is complete (find ST21NFCB_SOF_EOF at - * end of read) - * -EAGAIN : if received frame is incomplete (not find ST21NFCB_SOF_EOF - * at end of read) + * 0 : if received frame is complete * -EREMOTEIO : i2c read error (fatal) * -EBADMSG : frame was incorrect and discarded - * (value returned from st_nci_i2c_repack) - * -EIO : if no ST21NFCB_SOF_EOF is found after reaching - * the read length end sequence + * -ENOMEM : cannot allocate skb, frame dropped */ static int st_nci_i2c_read(struct st_nci_i2c_phy *phy, 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. * - * 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) { @@ -325,12 +320,12 @@ static int st_nci_i2c_probe(struct i2c_client *client, } } else { nfc_err(&client->dev, - "st21nfcb platform resources not available\n"); + "st_nci platform resources not available\n"); return -ENODEV; } 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); if (r < 0) { nfc_err(&client->dev, "Unable to register ndlc layer\n"); diff --git a/drivers/nfc/st-nci/ndlc.c b/drivers/nfc/st-nci/ndlc.c index 56c6a4cb4c96..d2cf84e680c6 100644 --- a/drivers/nfc/st-nci/ndlc.c +++ b/drivers/nfc/st-nci/ndlc.c @@ -171,6 +171,8 @@ static void llt_ndlc_rcv_queue(struct llt_ndlc *ndlc) if ((pcb & PCB_TYPE_MASK) == PCB_TYPE_SUPERVISOR) { switch (pcb & PCB_SYNC_MASK) { case PCB_SYNC_ACK: + skb = skb_dequeue(&ndlc->ack_pending_q); + kfree_skb(skb); del_timer_sync(&ndlc->t1_timer); del_timer_sync(&ndlc->t2_timer); 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)); break; default: - pr_err("UNKNOWN Packet Control Byte=%d\n", pcb); kfree_skb(skb); break; } - } else { + } else if ((pcb & PCB_TYPE_MASK) == PCB_TYPE_DATAFRAME) { nci_recv_frame(ndlc->ndev, skb); + } else { + kfree_skb(skb); } } } diff --git a/drivers/nfc/st-nci/spi.c b/drivers/nfc/st-nci/spi.c new file mode 100644 index 000000000000..598a58c4d6d1 --- /dev/null +++ b/drivers/nfc/st-nci/spi.c @@ -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); diff --git a/drivers/nfc/st-nci/st-nci_se.c b/drivers/nfc/st-nci/st-nci_se.c index 97addfa96c6f..c742ef65a05a 100644 --- a/drivers/nfc/st-nci/st-nci_se.c +++ b/drivers/nfc/st-nci/st-nci_se.c @@ -189,14 +189,14 @@ int st_nci_hci_load_session(struct nci_dev *ndev) ST_NCI_DEVICE_MGNT_GATE, ST_NCI_DEVICE_MGNT_PIPE); if (r < 0) - goto free_info; + return r; /* Get pipe list */ r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, ST_NCI_DM_GETINFO, pipe_list, sizeof(pipe_list), &skb_pipe_list); if (r < 0) - goto free_info; + return r; /* Complete the existing gate_pipe table */ 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) { pr_err("Unexpected apdu_reader pipe on host %x\n", dm_pipe_info->src_host_id); + kfree_skb(skb_pipe_info); 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 = dm_pipe_info->src_host_id; } + kfree_skb(skb_pipe_info); } memcpy(ndev->hci_dev->init_data.gates, st_nci_gates, sizeof(st_nci_gates)); -free_info: - kfree_skb(skb_pipe_info); kfree_skb(skb_pipe_list); return r; } diff --git a/drivers/nfc/st21nfca/st21nfca.c b/drivers/nfc/st21nfca/st21nfca.c index d251f7229c4e..051286562fab 100644 --- a/drivers/nfc/st21nfca/st21nfca.c +++ b/drivers/nfc/st21nfca/st21nfca.c @@ -148,14 +148,14 @@ static int st21nfca_hci_load_session(struct nfc_hci_dev *hdev) ST21NFCA_DEVICE_MGNT_GATE, ST21NFCA_DEVICE_MGNT_PIPE); if (r < 0) - goto free_info; + return r; /* Get pipe list */ r = nfc_hci_send_cmd(hdev, ST21NFCA_DEVICE_MGNT_GATE, ST21NFCA_DM_GETINFO, pipe_list, sizeof(pipe_list), &skb_pipe_list); if (r < 0) - goto free_info; + return r; /* Complete the existing gate_pipe table */ 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) { pr_err("Unexpected apdu_reader pipe on host %x\n", info->src_host_id); + kfree_skb(skb_pipe_info); continue; } @@ -200,6 +201,7 @@ static int st21nfca_hci_load_session(struct nfc_hci_dev *hdev) hdev->pipes[st21nfca_gates[j].pipe].dest_host = 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].pipe); if (r < 0) - goto free_info; + goto free_list; } } memcpy(hdev->init_data.gates, st21nfca_gates, sizeof(st21nfca_gates)); -free_info: - kfree_skb(skb_pipe_info); +free_list: kfree_skb(skb_pipe_list); return r; } diff --git a/drivers/nfc/trf7970a.c b/drivers/nfc/trf7970a.c index 85b4d86772d8..70b0707fd9a9 100644 --- a/drivers/nfc/trf7970a.c +++ b/drivers/nfc/trf7970a.c @@ -336,7 +336,7 @@ #define TRF7970A_NFC_TARGET_LEVEL_RFDET(v) ((v) & 0x07) #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_7BYTES (0x1 << 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) { - 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; } diff --git a/include/linux/platform_data/st_nci.h b/include/linux/platform_data/st_nci.h deleted file mode 100644 index d9d400a297bd..000000000000 --- a/include/linux/platform_data/st_nci.h +++ /dev/null @@ -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_ */ diff --git a/include/net/nfc/nci_core.h b/include/net/nfc/nci_core.h index 01fc8c531115..d0d0f1e53bb9 100644 --- a/include/net/nfc/nci_core.h +++ b/include/net/nfc/nci_core.h @@ -79,6 +79,7 @@ struct nci_ops { int (*close)(struct nci_dev *ndev); int (*send)(struct nci_dev *ndev, struct sk_buff *skb); int (*setup)(struct nci_dev *ndev); + int (*post_setup)(struct nci_dev *ndev); int (*fw_download)(struct nci_dev *ndev, const char *firmware_name); __u32 (*get_rfprotocol)(struct nci_dev *ndev, __u8 rf_protocol); int (*discover_se)(struct nci_dev *ndev); @@ -277,6 +278,8 @@ int nci_request(struct nci_dev *ndev, unsigned long opt), unsigned long opt, __u32 timeout); int nci_prop_cmd(struct nci_dev *ndev, __u8 oid, size_t len, __u8 *payload); +int nci_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_set_config(struct nci_dev *ndev, __u8 id, size_t len, __u8 *val); diff --git a/include/net/nfc/nfc.h b/include/net/nfc/nfc.h index f9e58ae45f9c..30afc9a6718c 100644 --- a/include/net/nfc/nfc.h +++ b/include/net/nfc/nfc.h @@ -203,6 +203,7 @@ struct nfc_dev { int n_vendor_cmds; struct nfc_ops *ops; + struct genl_info *cur_cmd_info; }; #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; } +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 */ diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c index 95af2d24d5be..943889b87a34 100644 --- a/net/nfc/nci/core.c +++ b/net/nfc/nci/core.c @@ -351,6 +351,20 @@ int nci_prop_cmd(struct nci_dev *ndev, __u8 oid, size_t len, __u8 *payload) } 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) { int rc = 0; @@ -388,6 +402,10 @@ static int nci_open_device(struct nci_dev *ndev) msecs_to_jiffies(NCI_INIT_TIMEOUT)); } + if (ndev->ops->post_setup) { + rc = ndev->ops->post_setup(ndev); + } + if (!rc) { rc = __nci_request(ndev, nci_init_complete_req, 0, msecs_to_jiffies(NCI_INIT_TIMEOUT)); diff --git a/net/nfc/nci/hci.c b/net/nfc/nci/hci.c index af002df640c7..609f92283d1b 100644 --- a/net/nfc/nci/hci.c +++ b/net/nfc/nci/hci.c @@ -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, msecs_to_jiffies(NCI_DATA_TIMEOUT)); - if (r == NCI_STATUS_OK) + if (r == NCI_STATUS_OK && skb) *skb = conn_info->rx_skb; return r; diff --git a/net/nfc/netlink.c b/net/nfc/netlink.c index f85f37ed19b2..853172c27f68 100644 --- a/net/nfc/netlink.c +++ b/net/nfc/netlink.c @@ -63,6 +63,8 @@ static const struct nla_policy nfc_genl_policy[NFC_ATTR_MAX + 1] = { [NFC_ATTR_FIRMWARE_NAME] = { .type = NLA_STRING, .len = NFC_FIRMWARE_NAME_MAXSIZE }, [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] = { @@ -1503,7 +1505,7 @@ static int nfc_genl_vendor_cmd(struct sk_buff *skb, u32 dev_idx, vid, subcmd; u8 *data; size_t data_len; - int i; + int i, err; if (!info->attrs[NFC_ATTR_DEVICE_INDEX] || !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) return -ENODEV; - data = nla_data(info->attrs[NFC_ATTR_VENDOR_DATA]); - if (data) { + if (info->attrs[NFC_ATTR_VENDOR_DATA]) { + data = nla_data(info->attrs[NFC_ATTR_VENDOR_DATA]); data_len = nla_len(info->attrs[NFC_ATTR_VENDOR_DATA]); if (data_len == 0) return -EINVAL; } else { + data = NULL; 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) 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; } +/* 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[] = { { .cmd = NFC_CMD_GET_DEVICE, |