1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Generic driver for NXP NCI NFC chips
4 *
5 * Copyright (C) 2014  NXP Semiconductors  All rights reserved.
6 *
7 * Authors: Clément Perrochaud <clement.perrochaud@nxp.com>
8 *
9 * Derived from PN544 device driver:
10 * Copyright (C) 2012  Intel Corporation. All rights reserved.
11 */
12
13#include <linux/delay.h>
14#include <linux/module.h>
15#include <linux/nfc.h>
16
17#include <net/nfc/nci_core.h>
18
19#include "nxp-nci.h"
20
21#define NXP_NCI_HDR_LEN	4
22
23#define NXP_NCI_NFC_PROTOCOLS (NFC_PROTO_JEWEL_MASK | \
24			       NFC_PROTO_MIFARE_MASK | \
25			       NFC_PROTO_FELICA_MASK | \
26			       NFC_PROTO_ISO14443_MASK | \
27			       NFC_PROTO_ISO14443_B_MASK | \
28			       NFC_PROTO_NFC_DEP_MASK)
29
30static int nxp_nci_open(struct nci_dev *ndev)
31{
32	struct nxp_nci_info *info = nci_get_drvdata(ndev);
33	int r = 0;
34
35	mutex_lock(&info->info_lock);
36
37	if (info->mode != NXP_NCI_MODE_COLD) {
38		r = -EBUSY;
39		goto open_exit;
40	}
41
42	if (info->phy_ops->set_mode)
43		r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_NCI);
44
45	info->mode = NXP_NCI_MODE_NCI;
46
47open_exit:
48	mutex_unlock(&info->info_lock);
49	return r;
50}
51
52static int nxp_nci_close(struct nci_dev *ndev)
53{
54	struct nxp_nci_info *info = nci_get_drvdata(ndev);
55	int r = 0;
56
57	mutex_lock(&info->info_lock);
58
59	if (info->phy_ops->set_mode)
60		r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD);
61
62	info->mode = NXP_NCI_MODE_COLD;
63
64	mutex_unlock(&info->info_lock);
65	return r;
66}
67
68static int nxp_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
69{
70	struct nxp_nci_info *info = nci_get_drvdata(ndev);
71	int r;
72
73	if (!info->phy_ops->write)
74		return -EOPNOTSUPP;
75
76	if (info->mode != NXP_NCI_MODE_NCI)
77		return -EINVAL;
78
79	r = info->phy_ops->write(info->phy_id, skb);
80	if (r < 0) {
81		kfree_skb(skb);
82		return r;
83	}
84
85	consume_skb(skb);
86	return 0;
87}
88
89static struct nci_ops nxp_nci_ops = {
90	.open = nxp_nci_open,
91	.close = nxp_nci_close,
92	.send = nxp_nci_send,
93	.fw_download = nxp_nci_fw_download,
94};
95
96int nxp_nci_probe(void *phy_id, struct device *pdev,
97		  const struct nxp_nci_phy_ops *phy_ops,
98		  unsigned int max_payload,
99		  struct nci_dev **ndev)
100{
101	struct nxp_nci_info *info;
102	int r;
103
104	info = devm_kzalloc(pdev, sizeof(struct nxp_nci_info), GFP_KERNEL);
105	if (!info)
106		return -ENOMEM;
107
108	info->phy_id = phy_id;
109	info->pdev = pdev;
110	info->phy_ops = phy_ops;
111	info->max_payload = max_payload;
112	INIT_WORK(&info->fw_info.work, nxp_nci_fw_work);
113	init_completion(&info->fw_info.cmd_completion);
114	mutex_init(&info->info_lock);
115
116	if (info->phy_ops->set_mode) {
117		r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD);
118		if (r < 0)
119			return r;
120	}
121
122	info->mode = NXP_NCI_MODE_COLD;
123
124	info->ndev = nci_allocate_device(&nxp_nci_ops, NXP_NCI_NFC_PROTOCOLS,
125					 NXP_NCI_HDR_LEN, 0);
126	if (!info->ndev)
127		return -ENOMEM;
128
129	nci_set_parent_dev(info->ndev, pdev);
130	nci_set_drvdata(info->ndev, info);
131	r = nci_register_device(info->ndev);
132	if (r < 0) {
133		nci_free_device(info->ndev);
134		return r;
135	}
136
137	*ndev = info->ndev;
138	return r;
139}
140EXPORT_SYMBOL(nxp_nci_probe);
141
142void nxp_nci_remove(struct nci_dev *ndev)
143{
144	struct nxp_nci_info *info = nci_get_drvdata(ndev);
145
146	if (info->mode == NXP_NCI_MODE_FW)
147		nxp_nci_fw_work_complete(info, -ESHUTDOWN);
148	cancel_work_sync(&info->fw_info.work);
149
150	mutex_lock(&info->info_lock);
151
152	if (info->phy_ops->set_mode)
153		info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD);
154
155	nci_unregister_device(ndev);
156	nci_free_device(ndev);
157
158	mutex_unlock(&info->info_lock);
159}
160EXPORT_SYMBOL(nxp_nci_remove);
161
162MODULE_LICENSE("GPL");
163MODULE_DESCRIPTION("NXP NCI NFC driver");
164MODULE_AUTHOR("Clément Perrochaud <clement.perrochaud@nxp.com>");
165