18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * NCI based driver for Samsung S3FWRN5 NFC chip 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2015 Samsung Electrnoics 68c2ecf20Sopenharmony_ci * Robert Baldyga <r.baldyga@samsung.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <net/nfc/nci_core.h> 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include "s3fwrn5.h" 138c2ecf20Sopenharmony_ci#include "firmware.h" 148c2ecf20Sopenharmony_ci#include "nci.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#define S3FWRN5_NFC_PROTOCOLS (NFC_PROTO_JEWEL_MASK | \ 178c2ecf20Sopenharmony_ci NFC_PROTO_MIFARE_MASK | \ 188c2ecf20Sopenharmony_ci NFC_PROTO_FELICA_MASK | \ 198c2ecf20Sopenharmony_ci NFC_PROTO_ISO14443_MASK | \ 208c2ecf20Sopenharmony_ci NFC_PROTO_ISO14443_B_MASK | \ 218c2ecf20Sopenharmony_ci NFC_PROTO_ISO15693_MASK) 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistatic int s3fwrn5_firmware_update(struct s3fwrn5_info *info) 248c2ecf20Sopenharmony_ci{ 258c2ecf20Sopenharmony_ci bool need_update; 268c2ecf20Sopenharmony_ci int ret; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci s3fwrn5_fw_init(&info->fw_info, "sec_s3fwrn5_firmware.bin"); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci /* Update firmware */ 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci s3fwrn5_set_wake(info, false); 338c2ecf20Sopenharmony_ci s3fwrn5_set_mode(info, S3FWRN5_MODE_FW); 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci ret = s3fwrn5_fw_setup(&info->fw_info); 368c2ecf20Sopenharmony_ci if (ret < 0) 378c2ecf20Sopenharmony_ci return ret; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci need_update = s3fwrn5_fw_check_version(&info->fw_info, 408c2ecf20Sopenharmony_ci info->ndev->manufact_specific_info); 418c2ecf20Sopenharmony_ci if (!need_update) 428c2ecf20Sopenharmony_ci goto out; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci dev_info(&info->ndev->nfc_dev->dev, "Detected new firmware version\n"); 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci ret = s3fwrn5_fw_download(&info->fw_info); 478c2ecf20Sopenharmony_ci if (ret < 0) 488c2ecf20Sopenharmony_ci goto out; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci /* Update RF configuration */ 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci s3fwrn5_set_mode(info, S3FWRN5_MODE_NCI); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci s3fwrn5_set_wake(info, true); 558c2ecf20Sopenharmony_ci ret = s3fwrn5_nci_rf_configure(info, "sec_s3fwrn5_rfreg.bin"); 568c2ecf20Sopenharmony_ci s3fwrn5_set_wake(info, false); 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ciout: 598c2ecf20Sopenharmony_ci s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD); 608c2ecf20Sopenharmony_ci s3fwrn5_fw_cleanup(&info->fw_info); 618c2ecf20Sopenharmony_ci return ret; 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic int s3fwrn5_nci_open(struct nci_dev *ndev) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci struct s3fwrn5_info *info = nci_get_drvdata(ndev); 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci if (s3fwrn5_get_mode(info) != S3FWRN5_MODE_COLD) 698c2ecf20Sopenharmony_ci return -EBUSY; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci s3fwrn5_set_mode(info, S3FWRN5_MODE_NCI); 728c2ecf20Sopenharmony_ci s3fwrn5_set_wake(info, true); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci return 0; 758c2ecf20Sopenharmony_ci} 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic int s3fwrn5_nci_close(struct nci_dev *ndev) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci struct s3fwrn5_info *info = nci_get_drvdata(ndev); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci s3fwrn5_set_wake(info, false); 828c2ecf20Sopenharmony_ci s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci return 0; 858c2ecf20Sopenharmony_ci} 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic int s3fwrn5_nci_send(struct nci_dev *ndev, struct sk_buff *skb) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci struct s3fwrn5_info *info = nci_get_drvdata(ndev); 908c2ecf20Sopenharmony_ci int ret; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci mutex_lock(&info->mutex); 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci if (s3fwrn5_get_mode(info) != S3FWRN5_MODE_NCI) { 958c2ecf20Sopenharmony_ci mutex_unlock(&info->mutex); 968c2ecf20Sopenharmony_ci return -EINVAL; 978c2ecf20Sopenharmony_ci } 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci ret = s3fwrn5_write(info, skb); 1008c2ecf20Sopenharmony_ci if (ret < 0) { 1018c2ecf20Sopenharmony_ci kfree_skb(skb); 1028c2ecf20Sopenharmony_ci mutex_unlock(&info->mutex); 1038c2ecf20Sopenharmony_ci return ret; 1048c2ecf20Sopenharmony_ci } 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci consume_skb(skb); 1078c2ecf20Sopenharmony_ci mutex_unlock(&info->mutex); 1088c2ecf20Sopenharmony_ci return 0; 1098c2ecf20Sopenharmony_ci} 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cistatic int s3fwrn5_nci_post_setup(struct nci_dev *ndev) 1128c2ecf20Sopenharmony_ci{ 1138c2ecf20Sopenharmony_ci struct s3fwrn5_info *info = nci_get_drvdata(ndev); 1148c2ecf20Sopenharmony_ci int ret; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci ret = s3fwrn5_firmware_update(info); 1178c2ecf20Sopenharmony_ci if (ret < 0) 1188c2ecf20Sopenharmony_ci goto out; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci /* NCI core reset */ 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci s3fwrn5_set_mode(info, S3FWRN5_MODE_NCI); 1238c2ecf20Sopenharmony_ci s3fwrn5_set_wake(info, true); 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci ret = nci_core_reset(info->ndev); 1268c2ecf20Sopenharmony_ci if (ret < 0) 1278c2ecf20Sopenharmony_ci goto out; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci ret = nci_core_init(info->ndev); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ciout: 1328c2ecf20Sopenharmony_ci return ret; 1338c2ecf20Sopenharmony_ci} 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_cistatic struct nci_ops s3fwrn5_nci_ops = { 1368c2ecf20Sopenharmony_ci .open = s3fwrn5_nci_open, 1378c2ecf20Sopenharmony_ci .close = s3fwrn5_nci_close, 1388c2ecf20Sopenharmony_ci .send = s3fwrn5_nci_send, 1398c2ecf20Sopenharmony_ci .post_setup = s3fwrn5_nci_post_setup, 1408c2ecf20Sopenharmony_ci}; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ciint s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev, 1438c2ecf20Sopenharmony_ci const struct s3fwrn5_phy_ops *phy_ops, unsigned int max_payload) 1448c2ecf20Sopenharmony_ci{ 1458c2ecf20Sopenharmony_ci struct s3fwrn5_info *info; 1468c2ecf20Sopenharmony_ci int ret; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci info = devm_kzalloc(pdev, sizeof(*info), GFP_KERNEL); 1498c2ecf20Sopenharmony_ci if (!info) 1508c2ecf20Sopenharmony_ci return -ENOMEM; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci info->phy_id = phy_id; 1538c2ecf20Sopenharmony_ci info->pdev = pdev; 1548c2ecf20Sopenharmony_ci info->phy_ops = phy_ops; 1558c2ecf20Sopenharmony_ci info->max_payload = max_payload; 1568c2ecf20Sopenharmony_ci mutex_init(&info->mutex); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD); 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci s3fwrn5_nci_get_prop_ops(&s3fwrn5_nci_ops.prop_ops, 1618c2ecf20Sopenharmony_ci &s3fwrn5_nci_ops.n_prop_ops); 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci info->ndev = nci_allocate_device(&s3fwrn5_nci_ops, 1648c2ecf20Sopenharmony_ci S3FWRN5_NFC_PROTOCOLS, 0, 0); 1658c2ecf20Sopenharmony_ci if (!info->ndev) 1668c2ecf20Sopenharmony_ci return -ENOMEM; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci nci_set_parent_dev(info->ndev, pdev); 1698c2ecf20Sopenharmony_ci nci_set_drvdata(info->ndev, info); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci ret = nci_register_device(info->ndev); 1728c2ecf20Sopenharmony_ci if (ret < 0) { 1738c2ecf20Sopenharmony_ci nci_free_device(info->ndev); 1748c2ecf20Sopenharmony_ci return ret; 1758c2ecf20Sopenharmony_ci } 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci info->fw_info.ndev = info->ndev; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci *ndev = info->ndev; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci return ret; 1828c2ecf20Sopenharmony_ci} 1838c2ecf20Sopenharmony_ciEXPORT_SYMBOL(s3fwrn5_probe); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_civoid s3fwrn5_remove(struct nci_dev *ndev) 1868c2ecf20Sopenharmony_ci{ 1878c2ecf20Sopenharmony_ci struct s3fwrn5_info *info = nci_get_drvdata(ndev); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci nci_unregister_device(ndev); 1928c2ecf20Sopenharmony_ci nci_free_device(ndev); 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ciEXPORT_SYMBOL(s3fwrn5_remove); 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ciint s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb, 1978c2ecf20Sopenharmony_ci enum s3fwrn5_mode mode) 1988c2ecf20Sopenharmony_ci{ 1998c2ecf20Sopenharmony_ci switch (mode) { 2008c2ecf20Sopenharmony_ci case S3FWRN5_MODE_NCI: 2018c2ecf20Sopenharmony_ci return nci_recv_frame(ndev, skb); 2028c2ecf20Sopenharmony_ci case S3FWRN5_MODE_FW: 2038c2ecf20Sopenharmony_ci return s3fwrn5_fw_recv_frame(ndev, skb); 2048c2ecf20Sopenharmony_ci default: 2058c2ecf20Sopenharmony_ci kfree_skb(skb); 2068c2ecf20Sopenharmony_ci return -ENODEV; 2078c2ecf20Sopenharmony_ci } 2088c2ecf20Sopenharmony_ci} 2098c2ecf20Sopenharmony_ciEXPORT_SYMBOL(s3fwrn5_recv_frame); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2128c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Samsung S3FWRN5 NFC driver"); 2138c2ecf20Sopenharmony_ciMODULE_AUTHOR("Robert Baldyga <r.baldyga@samsung.com>"); 214