1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * NCI based driver for Samsung S3FWRN5 NFC chip 4 * 5 * Copyright (C) 2015 Samsung Electrnoics 6 * Robert Baldyga <r.baldyga@samsung.com> 7 */ 8 9#include <linux/module.h> 10#include <net/nfc/nci_core.h> 11 12#include "s3fwrn5.h" 13#include "firmware.h" 14#include "nci.h" 15 16#define S3FWRN5_NFC_PROTOCOLS (NFC_PROTO_JEWEL_MASK | \ 17 NFC_PROTO_MIFARE_MASK | \ 18 NFC_PROTO_FELICA_MASK | \ 19 NFC_PROTO_ISO14443_MASK | \ 20 NFC_PROTO_ISO14443_B_MASK | \ 21 NFC_PROTO_ISO15693_MASK) 22 23static int s3fwrn5_firmware_update(struct s3fwrn5_info *info) 24{ 25 bool need_update; 26 int ret; 27 28 s3fwrn5_fw_init(&info->fw_info, "sec_s3fwrn5_firmware.bin"); 29 30 /* Update firmware */ 31 32 s3fwrn5_set_wake(info, false); 33 s3fwrn5_set_mode(info, S3FWRN5_MODE_FW); 34 35 ret = s3fwrn5_fw_setup(&info->fw_info); 36 if (ret < 0) 37 return ret; 38 39 need_update = s3fwrn5_fw_check_version(&info->fw_info, 40 info->ndev->manufact_specific_info); 41 if (!need_update) 42 goto out; 43 44 dev_info(&info->ndev->nfc_dev->dev, "Detected new firmware version\n"); 45 46 ret = s3fwrn5_fw_download(&info->fw_info); 47 if (ret < 0) 48 goto out; 49 50 /* Update RF configuration */ 51 52 s3fwrn5_set_mode(info, S3FWRN5_MODE_NCI); 53 54 s3fwrn5_set_wake(info, true); 55 ret = s3fwrn5_nci_rf_configure(info, "sec_s3fwrn5_rfreg.bin"); 56 s3fwrn5_set_wake(info, false); 57 58out: 59 s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD); 60 s3fwrn5_fw_cleanup(&info->fw_info); 61 return ret; 62} 63 64static int s3fwrn5_nci_open(struct nci_dev *ndev) 65{ 66 struct s3fwrn5_info *info = nci_get_drvdata(ndev); 67 68 if (s3fwrn5_get_mode(info) != S3FWRN5_MODE_COLD) 69 return -EBUSY; 70 71 s3fwrn5_set_mode(info, S3FWRN5_MODE_NCI); 72 s3fwrn5_set_wake(info, true); 73 74 return 0; 75} 76 77static int s3fwrn5_nci_close(struct nci_dev *ndev) 78{ 79 struct s3fwrn5_info *info = nci_get_drvdata(ndev); 80 81 s3fwrn5_set_wake(info, false); 82 s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD); 83 84 return 0; 85} 86 87static int s3fwrn5_nci_send(struct nci_dev *ndev, struct sk_buff *skb) 88{ 89 struct s3fwrn5_info *info = nci_get_drvdata(ndev); 90 int ret; 91 92 mutex_lock(&info->mutex); 93 94 if (s3fwrn5_get_mode(info) != S3FWRN5_MODE_NCI) { 95 mutex_unlock(&info->mutex); 96 return -EINVAL; 97 } 98 99 ret = s3fwrn5_write(info, skb); 100 if (ret < 0) { 101 kfree_skb(skb); 102 mutex_unlock(&info->mutex); 103 return ret; 104 } 105 106 consume_skb(skb); 107 mutex_unlock(&info->mutex); 108 return 0; 109} 110 111static int s3fwrn5_nci_post_setup(struct nci_dev *ndev) 112{ 113 struct s3fwrn5_info *info = nci_get_drvdata(ndev); 114 int ret; 115 116 ret = s3fwrn5_firmware_update(info); 117 if (ret < 0) 118 goto out; 119 120 /* NCI core reset */ 121 122 s3fwrn5_set_mode(info, S3FWRN5_MODE_NCI); 123 s3fwrn5_set_wake(info, true); 124 125 ret = nci_core_reset(info->ndev); 126 if (ret < 0) 127 goto out; 128 129 ret = nci_core_init(info->ndev); 130 131out: 132 return ret; 133} 134 135static struct nci_ops s3fwrn5_nci_ops = { 136 .open = s3fwrn5_nci_open, 137 .close = s3fwrn5_nci_close, 138 .send = s3fwrn5_nci_send, 139 .post_setup = s3fwrn5_nci_post_setup, 140}; 141 142int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev, 143 const struct s3fwrn5_phy_ops *phy_ops, unsigned int max_payload) 144{ 145 struct s3fwrn5_info *info; 146 int ret; 147 148 info = devm_kzalloc(pdev, sizeof(*info), GFP_KERNEL); 149 if (!info) 150 return -ENOMEM; 151 152 info->phy_id = phy_id; 153 info->pdev = pdev; 154 info->phy_ops = phy_ops; 155 info->max_payload = max_payload; 156 mutex_init(&info->mutex); 157 158 s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD); 159 160 s3fwrn5_nci_get_prop_ops(&s3fwrn5_nci_ops.prop_ops, 161 &s3fwrn5_nci_ops.n_prop_ops); 162 163 info->ndev = nci_allocate_device(&s3fwrn5_nci_ops, 164 S3FWRN5_NFC_PROTOCOLS, 0, 0); 165 if (!info->ndev) 166 return -ENOMEM; 167 168 nci_set_parent_dev(info->ndev, pdev); 169 nci_set_drvdata(info->ndev, info); 170 171 ret = nci_register_device(info->ndev); 172 if (ret < 0) { 173 nci_free_device(info->ndev); 174 return ret; 175 } 176 177 info->fw_info.ndev = info->ndev; 178 179 *ndev = info->ndev; 180 181 return ret; 182} 183EXPORT_SYMBOL(s3fwrn5_probe); 184 185void s3fwrn5_remove(struct nci_dev *ndev) 186{ 187 struct s3fwrn5_info *info = nci_get_drvdata(ndev); 188 189 s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD); 190 191 nci_unregister_device(ndev); 192 nci_free_device(ndev); 193} 194EXPORT_SYMBOL(s3fwrn5_remove); 195 196int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb, 197 enum s3fwrn5_mode mode) 198{ 199 switch (mode) { 200 case S3FWRN5_MODE_NCI: 201 return nci_recv_frame(ndev, skb); 202 case S3FWRN5_MODE_FW: 203 return s3fwrn5_fw_recv_frame(ndev, skb); 204 default: 205 kfree_skb(skb); 206 return -ENODEV; 207 } 208} 209EXPORT_SYMBOL(s3fwrn5_recv_frame); 210 211MODULE_LICENSE("GPL"); 212MODULE_DESCRIPTION("Samsung S3FWRN5 NFC driver"); 213MODULE_AUTHOR("Robert Baldyga <r.baldyga@samsung.com>"); 214