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 | |
23 | static int s3fwrn5_firmware_init(struct s3fwrn5_info *info) |
24 | { |
25 | struct s3fwrn5_fw_info *fw_info = &info->fw_info; |
26 | int ret; |
27 | |
28 | s3fwrn5_fw_init(fw_info, fw_name: "sec_s3fwrn5_firmware.bin" ); |
29 | |
30 | /* Get firmware data */ |
31 | ret = s3fwrn5_fw_request_firmware(fw_info); |
32 | if (ret < 0) |
33 | dev_err(&fw_info->ndev->nfc_dev->dev, |
34 | "Failed to get fw file, ret=%02x\n" , ret); |
35 | return ret; |
36 | } |
37 | |
38 | static int s3fwrn5_firmware_update(struct s3fwrn5_info *info) |
39 | { |
40 | bool need_update; |
41 | int ret; |
42 | |
43 | /* Update firmware */ |
44 | |
45 | s3fwrn5_set_wake(info, wake: false); |
46 | s3fwrn5_set_mode(info, mode: S3FWRN5_MODE_FW); |
47 | |
48 | ret = s3fwrn5_fw_setup(fw_info: &info->fw_info); |
49 | if (ret < 0) |
50 | return ret; |
51 | |
52 | need_update = s3fwrn5_fw_check_version(fw_info: &info->fw_info, |
53 | version: info->ndev->manufact_specific_info); |
54 | if (!need_update) |
55 | goto out; |
56 | |
57 | dev_info(&info->ndev->nfc_dev->dev, "Detected new firmware version\n" ); |
58 | |
59 | ret = s3fwrn5_fw_download(fw_info: &info->fw_info); |
60 | if (ret < 0) |
61 | goto out; |
62 | |
63 | /* Update RF configuration */ |
64 | |
65 | s3fwrn5_set_mode(info, mode: S3FWRN5_MODE_NCI); |
66 | |
67 | s3fwrn5_set_wake(info, wake: true); |
68 | ret = s3fwrn5_nci_rf_configure(info, fw_name: "sec_s3fwrn5_rfreg.bin" ); |
69 | s3fwrn5_set_wake(info, wake: false); |
70 | |
71 | out: |
72 | s3fwrn5_set_mode(info, mode: S3FWRN5_MODE_COLD); |
73 | s3fwrn5_fw_cleanup(fw_info: &info->fw_info); |
74 | return ret; |
75 | } |
76 | |
77 | static int s3fwrn5_nci_open(struct nci_dev *ndev) |
78 | { |
79 | struct s3fwrn5_info *info = nci_get_drvdata(ndev); |
80 | |
81 | if (s3fwrn5_get_mode(info) != S3FWRN5_MODE_COLD) |
82 | return -EBUSY; |
83 | |
84 | s3fwrn5_set_mode(info, mode: S3FWRN5_MODE_NCI); |
85 | s3fwrn5_set_wake(info, wake: true); |
86 | |
87 | return 0; |
88 | } |
89 | |
90 | static int s3fwrn5_nci_close(struct nci_dev *ndev) |
91 | { |
92 | struct s3fwrn5_info *info = nci_get_drvdata(ndev); |
93 | |
94 | s3fwrn5_set_wake(info, wake: false); |
95 | s3fwrn5_set_mode(info, mode: S3FWRN5_MODE_COLD); |
96 | |
97 | return 0; |
98 | } |
99 | |
100 | static int s3fwrn5_nci_send(struct nci_dev *ndev, struct sk_buff *skb) |
101 | { |
102 | struct s3fwrn5_info *info = nci_get_drvdata(ndev); |
103 | int ret; |
104 | |
105 | mutex_lock(&info->mutex); |
106 | |
107 | if (s3fwrn5_get_mode(info) != S3FWRN5_MODE_NCI) { |
108 | kfree_skb(skb); |
109 | mutex_unlock(lock: &info->mutex); |
110 | return -EINVAL; |
111 | } |
112 | |
113 | ret = s3fwrn5_write(info, skb); |
114 | if (ret < 0) { |
115 | kfree_skb(skb); |
116 | mutex_unlock(lock: &info->mutex); |
117 | return ret; |
118 | } |
119 | |
120 | consume_skb(skb); |
121 | mutex_unlock(lock: &info->mutex); |
122 | return 0; |
123 | } |
124 | |
125 | static int s3fwrn5_nci_post_setup(struct nci_dev *ndev) |
126 | { |
127 | struct s3fwrn5_info *info = nci_get_drvdata(ndev); |
128 | int ret; |
129 | |
130 | if (s3fwrn5_firmware_init(info)) { |
131 | //skip bootloader mode |
132 | return 0; |
133 | } |
134 | |
135 | ret = s3fwrn5_firmware_update(info); |
136 | if (ret < 0) |
137 | return ret; |
138 | |
139 | /* NCI core reset */ |
140 | |
141 | s3fwrn5_set_mode(info, mode: S3FWRN5_MODE_NCI); |
142 | s3fwrn5_set_wake(info, wake: true); |
143 | |
144 | ret = nci_core_reset(ndev: info->ndev); |
145 | if (ret < 0) |
146 | return ret; |
147 | |
148 | return nci_core_init(ndev: info->ndev); |
149 | } |
150 | |
151 | static const struct nci_ops s3fwrn5_nci_ops = { |
152 | .open = s3fwrn5_nci_open, |
153 | .close = s3fwrn5_nci_close, |
154 | .send = s3fwrn5_nci_send, |
155 | .post_setup = s3fwrn5_nci_post_setup, |
156 | .prop_ops = s3fwrn5_nci_prop_ops, |
157 | .n_prop_ops = ARRAY_SIZE(s3fwrn5_nci_prop_ops), |
158 | }; |
159 | |
160 | int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev, |
161 | const struct s3fwrn5_phy_ops *phy_ops) |
162 | { |
163 | struct s3fwrn5_info *info; |
164 | int ret; |
165 | |
166 | info = devm_kzalloc(dev: pdev, size: sizeof(*info), GFP_KERNEL); |
167 | if (!info) |
168 | return -ENOMEM; |
169 | |
170 | info->phy_id = phy_id; |
171 | info->pdev = pdev; |
172 | info->phy_ops = phy_ops; |
173 | mutex_init(&info->mutex); |
174 | |
175 | s3fwrn5_set_mode(info, mode: S3FWRN5_MODE_COLD); |
176 | |
177 | info->ndev = nci_allocate_device(ops: &s3fwrn5_nci_ops, |
178 | S3FWRN5_NFC_PROTOCOLS, tx_headroom: 0, tx_tailroom: 0); |
179 | if (!info->ndev) |
180 | return -ENOMEM; |
181 | |
182 | nci_set_parent_dev(ndev: info->ndev, dev: pdev); |
183 | nci_set_drvdata(ndev: info->ndev, data: info); |
184 | |
185 | ret = nci_register_device(ndev: info->ndev); |
186 | if (ret < 0) { |
187 | nci_free_device(ndev: info->ndev); |
188 | return ret; |
189 | } |
190 | |
191 | info->fw_info.ndev = info->ndev; |
192 | |
193 | *ndev = info->ndev; |
194 | |
195 | return ret; |
196 | } |
197 | EXPORT_SYMBOL(s3fwrn5_probe); |
198 | |
199 | void s3fwrn5_remove(struct nci_dev *ndev) |
200 | { |
201 | struct s3fwrn5_info *info = nci_get_drvdata(ndev); |
202 | |
203 | s3fwrn5_set_mode(info, mode: S3FWRN5_MODE_COLD); |
204 | |
205 | nci_unregister_device(ndev); |
206 | nci_free_device(ndev); |
207 | } |
208 | EXPORT_SYMBOL(s3fwrn5_remove); |
209 | |
210 | int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb, |
211 | enum s3fwrn5_mode mode) |
212 | { |
213 | switch (mode) { |
214 | case S3FWRN5_MODE_NCI: |
215 | return nci_recv_frame(ndev, skb); |
216 | case S3FWRN5_MODE_FW: |
217 | return s3fwrn5_fw_recv_frame(ndev, skb); |
218 | default: |
219 | kfree_skb(skb); |
220 | return -ENODEV; |
221 | } |
222 | } |
223 | EXPORT_SYMBOL(s3fwrn5_recv_frame); |
224 | |
225 | MODULE_LICENSE("GPL" ); |
226 | MODULE_DESCRIPTION("Samsung S3FWRN5 NFC driver" ); |
227 | MODULE_AUTHOR("Robert Baldyga <r.baldyga@samsung.com>" ); |
228 | |