]>
Commit | Line | Data |
---|---|---|
dece4585 CP |
1 | /* |
2 | * Generic driver for NXP NCI NFC chips | |
3 | * | |
4 | * Copyright (C) 2014 NXP Semiconductors All rights reserved. | |
5 | * | |
6 | * Author: Clément Perrochaud <clement.perrochaud@nxp.com> | |
7 | * | |
8 | * Derived from PN544 device driver: | |
9 | * Copyright (C) 2012 Intel Corporation. All rights reserved. | |
10 | * | |
11 | * This program is free software; you can redistribute it and/or modify it | |
12 | * under the terms and conditions of the GNU General Public License, | |
13 | * version 2, as published by the Free Software Foundation. | |
14 | * | |
15 | * This program is distributed in the hope that it will be useful, | |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | * GNU General Public License for more details. | |
19 | * | |
20 | * You should have received a copy of the GNU General Public License | |
21 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | |
22 | */ | |
23 | ||
24 | #include <linux/completion.h> | |
25 | #include <linux/firmware.h> | |
26 | #include <linux/nfc.h> | |
27 | #include <linux/unaligned/access_ok.h> | |
28 | ||
29 | #include "nxp-nci.h" | |
30 | ||
31 | /* Crypto operations can take up to 30 seconds */ | |
32 | #define NXP_NCI_FW_ANSWER_TIMEOUT msecs_to_jiffies(30000) | |
33 | ||
34 | #define NXP_NCI_FW_CMD_RESET 0xF0 | |
35 | #define NXP_NCI_FW_CMD_GETVERSION 0xF1 | |
36 | #define NXP_NCI_FW_CMD_CHECKINTEGRITY 0xE0 | |
37 | #define NXP_NCI_FW_CMD_WRITE 0xC0 | |
38 | #define NXP_NCI_FW_CMD_READ 0xA2 | |
39 | #define NXP_NCI_FW_CMD_GETSESSIONSTATE 0xF2 | |
40 | #define NXP_NCI_FW_CMD_LOG 0xA7 | |
41 | #define NXP_NCI_FW_CMD_FORCE 0xD0 | |
42 | #define NXP_NCI_FW_CMD_GET_DIE_ID 0xF4 | |
43 | ||
44 | #define NXP_NCI_FW_CHUNK_FLAG 0x0400 | |
45 | ||
46 | #define NXP_NCI_FW_RESULT_OK 0x00 | |
47 | #define NXP_NCI_FW_RESULT_INVALID_ADDR 0x01 | |
48 | #define NXP_NCI_FW_RESULT_GENERIC_ERROR 0x02 | |
49 | #define NXP_NCI_FW_RESULT_UNKNOWN_CMD 0x0B | |
50 | #define NXP_NCI_FW_RESULT_ABORTED_CMD 0x0C | |
51 | #define NXP_NCI_FW_RESULT_PLL_ERROR 0x0D | |
52 | #define NXP_NCI_FW_RESULT_ADDR_RANGE_OFL_ERROR 0x1E | |
53 | #define NXP_NCI_FW_RESULT_BUFFER_OFL_ERROR 0x1F | |
54 | #define NXP_NCI_FW_RESULT_MEM_BSY 0x20 | |
55 | #define NXP_NCI_FW_RESULT_SIGNATURE_ERROR 0x21 | |
56 | #define NXP_NCI_FW_RESULT_FIRMWARE_VERSION_ERROR 0x24 | |
57 | #define NXP_NCI_FW_RESULT_PROTOCOL_ERROR 0x28 | |
58 | #define NXP_NCI_FW_RESULT_SFWU_DEGRADED 0x2A | |
59 | #define NXP_NCI_FW_RESULT_PH_STATUS_FIRST_CHUNK 0x2D | |
60 | #define NXP_NCI_FW_RESULT_PH_STATUS_NEXT_CHUNK 0x2E | |
61 | #define NXP_NCI_FW_RESULT_PH_STATUS_INTERNAL_ERROR_5 0xC5 | |
62 | ||
63 | void nxp_nci_fw_work_complete(struct nxp_nci_info *info, int result) | |
64 | { | |
65 | struct nxp_nci_fw_info *fw_info = &info->fw_info; | |
66 | int r; | |
67 | ||
68 | if (info->phy_ops->set_mode) { | |
69 | r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD); | |
70 | if (r < 0 && result == 0) | |
71 | result = -r; | |
72 | } | |
73 | ||
74 | info->mode = NXP_NCI_MODE_COLD; | |
75 | ||
76 | if (fw_info->fw) { | |
77 | release_firmware(fw_info->fw); | |
78 | fw_info->fw = NULL; | |
79 | } | |
80 | ||
81 | nfc_fw_download_done(info->ndev->nfc_dev, fw_info->name, (u32) -result); | |
82 | } | |
83 | ||
84 | /* crc_ccitt cannot be used since it is computed MSB first and not LSB first */ | |
85 | static u16 nxp_nci_fw_crc(u8 const *buffer, size_t len) | |
86 | { | |
87 | u16 crc = 0xffff; | |
88 | ||
89 | while (len--) { | |
90 | crc = ((crc >> 8) | (crc << 8)) ^ *buffer++; | |
91 | crc ^= (crc & 0xff) >> 4; | |
92 | crc ^= (crc & 0xff) << 12; | |
93 | crc ^= (crc & 0xff) << 5; | |
94 | } | |
95 | ||
96 | return crc; | |
97 | } | |
98 | ||
99 | static int nxp_nci_fw_send_chunk(struct nxp_nci_info *info) | |
100 | { | |
101 | struct nxp_nci_fw_info *fw_info = &info->fw_info; | |
102 | u16 header, crc; | |
103 | struct sk_buff *skb; | |
104 | size_t chunk_len; | |
105 | size_t remaining_len; | |
106 | int r; | |
107 | ||
108 | skb = nci_skb_alloc(info->ndev, info->max_payload, GFP_KERNEL); | |
109 | if (!skb) { | |
110 | r = -ENOMEM; | |
111 | goto chunk_exit; | |
112 | } | |
113 | ||
114 | chunk_len = info->max_payload - NXP_NCI_FW_HDR_LEN - NXP_NCI_FW_CRC_LEN; | |
115 | remaining_len = fw_info->frame_size - fw_info->written; | |
116 | ||
117 | if (remaining_len > chunk_len) { | |
118 | header = NXP_NCI_FW_CHUNK_FLAG; | |
119 | } else { | |
120 | chunk_len = remaining_len; | |
121 | header = 0x0000; | |
122 | } | |
123 | ||
124 | header |= chunk_len & NXP_NCI_FW_FRAME_LEN_MASK; | |
125 | put_unaligned_be16(header, skb_put(skb, NXP_NCI_FW_HDR_LEN)); | |
126 | ||
127 | memcpy(skb_put(skb, chunk_len), fw_info->data + fw_info->written, | |
128 | chunk_len); | |
129 | ||
130 | crc = nxp_nci_fw_crc(skb->data, chunk_len + NXP_NCI_FW_HDR_LEN); | |
131 | put_unaligned_be16(crc, skb_put(skb, NXP_NCI_FW_CRC_LEN)); | |
132 | ||
133 | r = info->phy_ops->write(info->phy_id, skb); | |
134 | if (r >= 0) | |
135 | r = chunk_len; | |
136 | ||
137 | kfree_skb(skb); | |
138 | ||
139 | chunk_exit: | |
140 | return r; | |
141 | } | |
142 | ||
143 | static int nxp_nci_fw_send(struct nxp_nci_info *info) | |
144 | { | |
145 | struct nxp_nci_fw_info *fw_info = &info->fw_info; | |
146 | long completion_rc; | |
147 | int r; | |
148 | ||
149 | reinit_completion(&fw_info->cmd_completion); | |
150 | ||
151 | if (fw_info->written == 0) { | |
152 | fw_info->frame_size = get_unaligned_be16(fw_info->data) & | |
153 | NXP_NCI_FW_FRAME_LEN_MASK; | |
154 | fw_info->data += NXP_NCI_FW_HDR_LEN; | |
155 | fw_info->size -= NXP_NCI_FW_HDR_LEN; | |
156 | } | |
157 | ||
158 | if (fw_info->frame_size > fw_info->size) | |
159 | return -EMSGSIZE; | |
160 | ||
161 | r = nxp_nci_fw_send_chunk(info); | |
162 | if (r < 0) | |
163 | return r; | |
164 | ||
165 | fw_info->written += r; | |
166 | ||
167 | if (*fw_info->data == NXP_NCI_FW_CMD_RESET) { | |
168 | fw_info->cmd_result = 0; | |
169 | if (fw_info->fw) | |
170 | schedule_work(&fw_info->work); | |
171 | } else { | |
172 | completion_rc = wait_for_completion_interruptible_timeout( | |
173 | &fw_info->cmd_completion, NXP_NCI_FW_ANSWER_TIMEOUT); | |
174 | if (completion_rc == 0) | |
175 | return -ETIMEDOUT; | |
176 | } | |
177 | ||
178 | return 0; | |
179 | } | |
180 | ||
181 | void nxp_nci_fw_work(struct work_struct *work) | |
182 | { | |
183 | struct nxp_nci_info *info; | |
184 | struct nxp_nci_fw_info *fw_info; | |
185 | int r; | |
186 | ||
187 | fw_info = container_of(work, struct nxp_nci_fw_info, work); | |
188 | info = container_of(fw_info, struct nxp_nci_info, fw_info); | |
189 | ||
190 | mutex_lock(&info->info_lock); | |
191 | ||
192 | r = fw_info->cmd_result; | |
193 | if (r < 0) | |
194 | goto exit_work; | |
195 | ||
196 | if (fw_info->written == fw_info->frame_size) { | |
197 | fw_info->data += fw_info->frame_size; | |
198 | fw_info->size -= fw_info->frame_size; | |
199 | fw_info->written = 0; | |
200 | } | |
201 | ||
202 | if (fw_info->size > 0) | |
203 | r = nxp_nci_fw_send(info); | |
204 | ||
205 | exit_work: | |
206 | if (r < 0 || fw_info->size == 0) | |
207 | nxp_nci_fw_work_complete(info, r); | |
208 | mutex_unlock(&info->info_lock); | |
209 | } | |
210 | ||
211 | int nxp_nci_fw_download(struct nci_dev *ndev, const char *firmware_name) | |
212 | { | |
213 | struct nxp_nci_info *info = nci_get_drvdata(ndev); | |
214 | struct nxp_nci_fw_info *fw_info = &info->fw_info; | |
215 | int r; | |
216 | ||
217 | mutex_lock(&info->info_lock); | |
218 | ||
219 | if (!info->phy_ops->set_mode || !info->phy_ops->write) { | |
220 | r = -ENOTSUPP; | |
221 | goto fw_download_exit; | |
222 | } | |
223 | ||
224 | if (!firmware_name || firmware_name[0] == '\0') { | |
225 | r = -EINVAL; | |
226 | goto fw_download_exit; | |
227 | } | |
228 | ||
229 | strcpy(fw_info->name, firmware_name); | |
230 | ||
231 | r = request_firmware(&fw_info->fw, firmware_name, | |
232 | ndev->nfc_dev->dev.parent); | |
233 | if (r < 0) | |
234 | goto fw_download_exit; | |
235 | ||
236 | r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_FW); | |
9421ce10 SO |
237 | if (r < 0) { |
238 | release_firmware(fw_info->fw); | |
dece4585 | 239 | goto fw_download_exit; |
9421ce10 | 240 | } |
dece4585 CP |
241 | |
242 | info->mode = NXP_NCI_MODE_FW; | |
243 | ||
244 | fw_info->data = fw_info->fw->data; | |
245 | fw_info->size = fw_info->fw->size; | |
246 | fw_info->written = 0; | |
247 | fw_info->frame_size = 0; | |
248 | fw_info->cmd_result = 0; | |
249 | ||
2b591257 | 250 | schedule_work(&fw_info->work); |
dece4585 CP |
251 | |
252 | fw_download_exit: | |
253 | mutex_unlock(&info->info_lock); | |
254 | return r; | |
255 | } | |
256 | ||
257 | static int nxp_nci_fw_read_status(u8 stat) | |
258 | { | |
259 | switch (stat) { | |
260 | case NXP_NCI_FW_RESULT_OK: | |
261 | return 0; | |
262 | case NXP_NCI_FW_RESULT_INVALID_ADDR: | |
263 | return -EINVAL; | |
264 | case NXP_NCI_FW_RESULT_UNKNOWN_CMD: | |
265 | return -EINVAL; | |
266 | case NXP_NCI_FW_RESULT_ABORTED_CMD: | |
267 | return -EMSGSIZE; | |
268 | case NXP_NCI_FW_RESULT_ADDR_RANGE_OFL_ERROR: | |
269 | return -EADDRNOTAVAIL; | |
270 | case NXP_NCI_FW_RESULT_BUFFER_OFL_ERROR: | |
271 | return -ENOBUFS; | |
272 | case NXP_NCI_FW_RESULT_MEM_BSY: | |
273 | return -ENOKEY; | |
274 | case NXP_NCI_FW_RESULT_SIGNATURE_ERROR: | |
275 | return -EKEYREJECTED; | |
276 | case NXP_NCI_FW_RESULT_FIRMWARE_VERSION_ERROR: | |
277 | return -EALREADY; | |
278 | case NXP_NCI_FW_RESULT_PROTOCOL_ERROR: | |
279 | return -EPROTO; | |
280 | case NXP_NCI_FW_RESULT_SFWU_DEGRADED: | |
281 | return -EHWPOISON; | |
282 | case NXP_NCI_FW_RESULT_PH_STATUS_FIRST_CHUNK: | |
283 | return 0; | |
284 | case NXP_NCI_FW_RESULT_PH_STATUS_NEXT_CHUNK: | |
285 | return 0; | |
286 | case NXP_NCI_FW_RESULT_PH_STATUS_INTERNAL_ERROR_5: | |
287 | return -EINVAL; | |
288 | default: | |
289 | return -EIO; | |
290 | } | |
291 | } | |
292 | ||
293 | static u16 nxp_nci_fw_check_crc(struct sk_buff *skb) | |
294 | { | |
295 | u16 crc, frame_crc; | |
296 | size_t len = skb->len - NXP_NCI_FW_CRC_LEN; | |
297 | ||
298 | crc = nxp_nci_fw_crc(skb->data, len); | |
299 | frame_crc = get_unaligned_be16(skb->data + len); | |
300 | ||
301 | return (crc ^ frame_crc); | |
302 | } | |
303 | ||
304 | void nxp_nci_fw_recv_frame(struct nci_dev *ndev, struct sk_buff *skb) | |
305 | { | |
306 | struct nxp_nci_info *info = nci_get_drvdata(ndev); | |
307 | struct nxp_nci_fw_info *fw_info = &info->fw_info; | |
308 | ||
309 | complete(&fw_info->cmd_completion); | |
310 | ||
311 | if (skb) { | |
312 | if (nxp_nci_fw_check_crc(skb) != 0x00) | |
313 | fw_info->cmd_result = -EBADMSG; | |
314 | else | |
315 | fw_info->cmd_result = nxp_nci_fw_read_status( | |
316 | *skb_pull(skb, NXP_NCI_FW_HDR_LEN)); | |
317 | kfree_skb(skb); | |
318 | } else { | |
319 | fw_info->cmd_result = -EIO; | |
320 | } | |
321 | ||
322 | if (fw_info->fw) | |
323 | schedule_work(&fw_info->work); | |
324 | } | |
325 | EXPORT_SYMBOL(nxp_nci_fw_recv_frame); |