| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * NCI based Driver for STMicroelectronics NFC Chip | |
| * | |
| * Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved. | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/nfc.h> | |
| #include <net/nfc/nci.h> | |
| #include <net/nfc/nci_core.h> | |
| #include "st-nci.h" | |
| #define DRIVER_DESC "NCI NFC driver for ST_NCI" | |
| #define ST_NCI1_X_PROPRIETARY_ISO15693 0x83 | |
| static int st_nci_init(struct nci_dev *ndev) | |
| { | |
| struct nci_mode_set_cmd cmd; | |
| cmd.cmd_type = ST_NCI_SET_NFC_MODE; | |
| cmd.mode = 1; | |
| return nci_prop_cmd(ndev, ST_NCI_CORE_PROP, | |
| sizeof(struct nci_mode_set_cmd), (__u8 *)&cmd); | |
| } | |
| static int st_nci_open(struct nci_dev *ndev) | |
| { | |
| struct st_nci_info *info = nci_get_drvdata(ndev); | |
| int r; | |
| if (test_and_set_bit(ST_NCI_RUNNING, &info->flags)) | |
| return 0; | |
| r = ndlc_open(info->ndlc); | |
| if (r) | |
| clear_bit(ST_NCI_RUNNING, &info->flags); | |
| return r; | |
| } | |
| static int st_nci_close(struct nci_dev *ndev) | |
| { | |
| struct st_nci_info *info = nci_get_drvdata(ndev); | |
| if (!test_bit(ST_NCI_RUNNING, &info->flags)) | |
| return 0; | |
| ndlc_close(info->ndlc); | |
| clear_bit(ST_NCI_RUNNING, &info->flags); | |
| return 0; | |
| } | |
| static int st_nci_send(struct nci_dev *ndev, struct sk_buff *skb) | |
| { | |
| struct st_nci_info *info = nci_get_drvdata(ndev); | |
| skb->dev = (void *)ndev; | |
| if (!test_bit(ST_NCI_RUNNING, &info->flags)) | |
| return -EBUSY; | |
| return ndlc_send(info->ndlc, skb); | |
| } | |
| static __u32 st_nci_get_rfprotocol(struct nci_dev *ndev, | |
| __u8 rf_protocol) | |
| { | |
| return rf_protocol == ST_NCI1_X_PROPRIETARY_ISO15693 ? | |
| NFC_PROTO_ISO15693_MASK : 0; | |
| } | |
| static int st_nci_prop_rsp_packet(struct nci_dev *ndev, | |
| struct sk_buff *skb) | |
| { | |
| __u8 status = skb->data[0]; | |
| nci_req_complete(ndev, status); | |
| return 0; | |
| } | |
| static const struct nci_driver_ops st_nci_prop_ops[] = { | |
| { | |
| .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, | |
| ST_NCI_CORE_PROP), | |
| .rsp = st_nci_prop_rsp_packet, | |
| }, | |
| }; | |
| static const struct nci_ops st_nci_ops = { | |
| .init = st_nci_init, | |
| .open = st_nci_open, | |
| .close = st_nci_close, | |
| .send = st_nci_send, | |
| .get_rfprotocol = st_nci_get_rfprotocol, | |
| .discover_se = st_nci_discover_se, | |
| .enable_se = st_nci_enable_se, | |
| .disable_se = st_nci_disable_se, | |
| .se_io = st_nci_se_io, | |
| .hci_load_session = st_nci_hci_load_session, | |
| .hci_event_received = st_nci_hci_event_received, | |
| .hci_cmd_received = st_nci_hci_cmd_received, | |
| .prop_ops = st_nci_prop_ops, | |
| .n_prop_ops = ARRAY_SIZE(st_nci_prop_ops), | |
| }; | |
| int st_nci_probe(struct llt_ndlc *ndlc, int phy_headroom, | |
| int phy_tailroom, struct st_nci_se_status *se_status) | |
| { | |
| struct st_nci_info *info; | |
| int r; | |
| u32 protocols; | |
| info = devm_kzalloc(ndlc->dev, | |
| sizeof(struct st_nci_info), GFP_KERNEL); | |
| if (!info) | |
| return -ENOMEM; | |
| protocols = NFC_PROTO_JEWEL_MASK | |
| | NFC_PROTO_MIFARE_MASK | |
| | NFC_PROTO_FELICA_MASK | |
| | NFC_PROTO_ISO14443_MASK | |
| | NFC_PROTO_ISO14443_B_MASK | |
| | NFC_PROTO_ISO15693_MASK | |
| | NFC_PROTO_NFC_DEP_MASK; | |
| BUILD_BUG_ON(ARRAY_SIZE(st_nci_prop_ops) > NCI_MAX_PROPRIETARY_CMD); | |
| ndlc->ndev = nci_allocate_device(&st_nci_ops, protocols, | |
| phy_headroom, phy_tailroom); | |
| if (!ndlc->ndev) { | |
| pr_err("Cannot allocate nfc ndev\n"); | |
| return -ENOMEM; | |
| } | |
| info->ndlc = ndlc; | |
| nci_set_drvdata(ndlc->ndev, info); | |
| r = st_nci_vendor_cmds_init(ndlc->ndev); | |
| if (r) { | |
| pr_err("Cannot register proprietary vendor cmds\n"); | |
| goto err_reg_dev; | |
| } | |
| r = nci_register_device(ndlc->ndev); | |
| if (r) { | |
| pr_err("Cannot register nfc device to nci core\n"); | |
| goto err_reg_dev; | |
| } | |
| return st_nci_se_init(ndlc->ndev, se_status); | |
| err_reg_dev: | |
| nci_free_device(ndlc->ndev); | |
| return r; | |
| } | |
| EXPORT_SYMBOL_GPL(st_nci_probe); | |
| void st_nci_remove(struct nci_dev *ndev) | |
| { | |
| struct st_nci_info *info = nci_get_drvdata(ndev); | |
| ndlc_close(info->ndlc); | |
| nci_unregister_device(ndev); | |
| nci_free_device(ndev); | |
| } | |
| EXPORT_SYMBOL_GPL(st_nci_remove); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_DESCRIPTION(DRIVER_DESC); |