1 | /* |
2 | * Copyright (c) 2004-2011 Atheros Communications Inc. |
3 | * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. |
4 | * |
5 | * Permission to use, copy, modify, and/or distribute this software for any |
6 | * purpose with or without fee is hereby granted, provided that the above |
7 | * copyright notice and this permission notice appear in all copies. |
8 | * |
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | */ |
17 | |
18 | #include "core.h" |
19 | #include "hif-ops.h" |
20 | #include "target.h" |
21 | #include "debug.h" |
22 | |
23 | int ath6kl_bmi_done(struct ath6kl *ar) |
24 | { |
25 | int ret; |
26 | u32 cid = BMI_DONE; |
27 | |
28 | if (ar->bmi.done_sent) { |
29 | ath6kl_dbg(mask: ATH6KL_DBG_BMI, fmt: "bmi done skipped\n" ); |
30 | return 0; |
31 | } |
32 | |
33 | ar->bmi.done_sent = true; |
34 | |
35 | ret = ath6kl_hif_bmi_write(ar, buf: (u8 *)&cid, len: sizeof(cid)); |
36 | if (ret) { |
37 | ath6kl_err(fmt: "Unable to send bmi done: %d\n" , ret); |
38 | return ret; |
39 | } |
40 | |
41 | return 0; |
42 | } |
43 | |
44 | int ath6kl_bmi_get_target_info(struct ath6kl *ar, |
45 | struct ath6kl_bmi_target_info *targ_info) |
46 | { |
47 | int ret; |
48 | u32 cid = BMI_GET_TARGET_INFO; |
49 | |
50 | if (ar->bmi.done_sent) { |
51 | ath6kl_err(fmt: "bmi done sent already, cmd %d disallowed\n" , cid); |
52 | return -EACCES; |
53 | } |
54 | |
55 | ret = ath6kl_hif_bmi_write(ar, buf: (u8 *)&cid, len: sizeof(cid)); |
56 | if (ret) { |
57 | ath6kl_err(fmt: "Unable to send get target info: %d\n" , ret); |
58 | return ret; |
59 | } |
60 | |
61 | if (ar->hif_type == ATH6KL_HIF_TYPE_USB) { |
62 | ret = ath6kl_hif_bmi_read(ar, buf: (u8 *)targ_info, |
63 | len: sizeof(*targ_info)); |
64 | } else { |
65 | ret = ath6kl_hif_bmi_read(ar, buf: (u8 *)&targ_info->version, |
66 | len: sizeof(targ_info->version)); |
67 | } |
68 | |
69 | if (ret) { |
70 | ath6kl_err(fmt: "Unable to recv target info: %d\n" , ret); |
71 | return ret; |
72 | } |
73 | |
74 | if (le32_to_cpu(targ_info->version) == TARGET_VERSION_SENTINAL) { |
75 | /* Determine how many bytes are in the Target's targ_info */ |
76 | ret = ath6kl_hif_bmi_read(ar, |
77 | buf: (u8 *)&targ_info->byte_count, |
78 | len: sizeof(targ_info->byte_count)); |
79 | if (ret) { |
80 | ath6kl_err(fmt: "unable to read target info byte count: %d\n" , |
81 | ret); |
82 | return ret; |
83 | } |
84 | |
85 | /* |
86 | * The target's targ_info doesn't match the host's targ_info. |
87 | * We need to do some backwards compatibility to make this work. |
88 | */ |
89 | if (le32_to_cpu(targ_info->byte_count) != sizeof(*targ_info)) { |
90 | WARN_ON(1); |
91 | return -EINVAL; |
92 | } |
93 | |
94 | /* Read the remainder of the targ_info */ |
95 | ret = ath6kl_hif_bmi_read(ar, |
96 | buf: ((u8 *)targ_info) + |
97 | sizeof(targ_info->byte_count), |
98 | len: sizeof(*targ_info) - |
99 | sizeof(targ_info->byte_count)); |
100 | |
101 | if (ret) { |
102 | ath6kl_err(fmt: "Unable to read target info (%d bytes): %d\n" , |
103 | targ_info->byte_count, ret); |
104 | return ret; |
105 | } |
106 | } |
107 | |
108 | ath6kl_dbg(mask: ATH6KL_DBG_BMI, fmt: "target info (ver: 0x%x type: 0x%x)\n" , |
109 | targ_info->version, targ_info->type); |
110 | |
111 | return 0; |
112 | } |
113 | |
114 | int ath6kl_bmi_read(struct ath6kl *ar, u32 addr, u8 *buf, u32 len) |
115 | { |
116 | u32 cid = BMI_READ_MEMORY; |
117 | int ret; |
118 | u32 offset; |
119 | u32 len_remain, rx_len; |
120 | u16 size; |
121 | |
122 | if (ar->bmi.done_sent) { |
123 | ath6kl_err(fmt: "bmi done sent already, cmd %d disallowed\n" , cid); |
124 | return -EACCES; |
125 | } |
126 | |
127 | size = ar->bmi.max_data_size + sizeof(cid) + sizeof(addr) + sizeof(len); |
128 | if (size > ar->bmi.max_cmd_size) { |
129 | WARN_ON(1); |
130 | return -EINVAL; |
131 | } |
132 | memset(ar->bmi.cmd_buf, 0, size); |
133 | |
134 | ath6kl_dbg(mask: ATH6KL_DBG_BMI, |
135 | fmt: "bmi read memory: device: addr: 0x%x, len: %d\n" , |
136 | addr, len); |
137 | |
138 | len_remain = len; |
139 | |
140 | while (len_remain) { |
141 | rx_len = (len_remain < ar->bmi.max_data_size) ? |
142 | len_remain : ar->bmi.max_data_size; |
143 | offset = 0; |
144 | memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); |
145 | offset += sizeof(cid); |
146 | memcpy(&(ar->bmi.cmd_buf[offset]), &addr, sizeof(addr)); |
147 | offset += sizeof(addr); |
148 | memcpy(&(ar->bmi.cmd_buf[offset]), &rx_len, sizeof(rx_len)); |
149 | offset += sizeof(len); |
150 | |
151 | ret = ath6kl_hif_bmi_write(ar, buf: ar->bmi.cmd_buf, len: offset); |
152 | if (ret) { |
153 | ath6kl_err(fmt: "Unable to write to the device: %d\n" , |
154 | ret); |
155 | return ret; |
156 | } |
157 | ret = ath6kl_hif_bmi_read(ar, buf: ar->bmi.cmd_buf, len: rx_len); |
158 | if (ret) { |
159 | ath6kl_err(fmt: "Unable to read from the device: %d\n" , |
160 | ret); |
161 | return ret; |
162 | } |
163 | memcpy(&buf[len - len_remain], ar->bmi.cmd_buf, rx_len); |
164 | len_remain -= rx_len; addr += rx_len; |
165 | } |
166 | |
167 | return 0; |
168 | } |
169 | |
170 | int ath6kl_bmi_write(struct ath6kl *ar, u32 addr, u8 *buf, u32 len) |
171 | { |
172 | u32 cid = BMI_WRITE_MEMORY; |
173 | int ret; |
174 | u32 offset; |
175 | u32 len_remain, tx_len; |
176 | const u32 = sizeof(cid) + sizeof(addr) + sizeof(len); |
177 | u8 aligned_buf[400]; |
178 | u8 *src; |
179 | |
180 | if (ar->bmi.done_sent) { |
181 | ath6kl_err(fmt: "bmi done sent already, cmd %d disallowed\n" , cid); |
182 | return -EACCES; |
183 | } |
184 | |
185 | if ((ar->bmi.max_data_size + header) > ar->bmi.max_cmd_size) { |
186 | WARN_ON(1); |
187 | return -EINVAL; |
188 | } |
189 | |
190 | if (WARN_ON(ar->bmi.max_data_size > sizeof(aligned_buf))) |
191 | return -E2BIG; |
192 | |
193 | memset(ar->bmi.cmd_buf, 0, ar->bmi.max_data_size + header); |
194 | |
195 | ath6kl_dbg(mask: ATH6KL_DBG_BMI, |
196 | fmt: "bmi write memory: addr: 0x%x, len: %d\n" , addr, len); |
197 | |
198 | len_remain = len; |
199 | while (len_remain) { |
200 | src = &buf[len - len_remain]; |
201 | |
202 | if (len_remain < (ar->bmi.max_data_size - header)) { |
203 | if (len_remain & 3) { |
204 | /* align it with 4 bytes */ |
205 | len_remain = len_remain + |
206 | (4 - (len_remain & 3)); |
207 | memcpy(aligned_buf, src, len_remain); |
208 | src = aligned_buf; |
209 | } |
210 | tx_len = len_remain; |
211 | } else { |
212 | tx_len = (ar->bmi.max_data_size - header); |
213 | } |
214 | |
215 | offset = 0; |
216 | memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); |
217 | offset += sizeof(cid); |
218 | memcpy(&(ar->bmi.cmd_buf[offset]), &addr, sizeof(addr)); |
219 | offset += sizeof(addr); |
220 | memcpy(&(ar->bmi.cmd_buf[offset]), &tx_len, sizeof(tx_len)); |
221 | offset += sizeof(tx_len); |
222 | memcpy(&(ar->bmi.cmd_buf[offset]), src, tx_len); |
223 | offset += tx_len; |
224 | |
225 | ret = ath6kl_hif_bmi_write(ar, buf: ar->bmi.cmd_buf, len: offset); |
226 | if (ret) { |
227 | ath6kl_err(fmt: "Unable to write to the device: %d\n" , |
228 | ret); |
229 | return ret; |
230 | } |
231 | len_remain -= tx_len; addr += tx_len; |
232 | } |
233 | |
234 | return 0; |
235 | } |
236 | |
237 | int ath6kl_bmi_execute(struct ath6kl *ar, u32 addr, u32 *param) |
238 | { |
239 | u32 cid = BMI_EXECUTE; |
240 | int ret; |
241 | u32 offset; |
242 | u16 size; |
243 | |
244 | if (ar->bmi.done_sent) { |
245 | ath6kl_err(fmt: "bmi done sent already, cmd %d disallowed\n" , cid); |
246 | return -EACCES; |
247 | } |
248 | |
249 | size = sizeof(cid) + sizeof(addr) + sizeof(*param); |
250 | if (size > ar->bmi.max_cmd_size) { |
251 | WARN_ON(1); |
252 | return -EINVAL; |
253 | } |
254 | memset(ar->bmi.cmd_buf, 0, size); |
255 | |
256 | ath6kl_dbg(mask: ATH6KL_DBG_BMI, fmt: "bmi execute: addr: 0x%x, param: %d)\n" , |
257 | addr, *param); |
258 | |
259 | offset = 0; |
260 | memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); |
261 | offset += sizeof(cid); |
262 | memcpy(&(ar->bmi.cmd_buf[offset]), &addr, sizeof(addr)); |
263 | offset += sizeof(addr); |
264 | memcpy(&(ar->bmi.cmd_buf[offset]), param, sizeof(*param)); |
265 | offset += sizeof(*param); |
266 | |
267 | ret = ath6kl_hif_bmi_write(ar, buf: ar->bmi.cmd_buf, len: offset); |
268 | if (ret) { |
269 | ath6kl_err(fmt: "Unable to write to the device: %d\n" , ret); |
270 | return ret; |
271 | } |
272 | |
273 | ret = ath6kl_hif_bmi_read(ar, buf: ar->bmi.cmd_buf, len: sizeof(*param)); |
274 | if (ret) { |
275 | ath6kl_err(fmt: "Unable to read from the device: %d\n" , ret); |
276 | return ret; |
277 | } |
278 | |
279 | memcpy(param, ar->bmi.cmd_buf, sizeof(*param)); |
280 | |
281 | return 0; |
282 | } |
283 | |
284 | int ath6kl_bmi_set_app_start(struct ath6kl *ar, u32 addr) |
285 | { |
286 | u32 cid = BMI_SET_APP_START; |
287 | int ret; |
288 | u32 offset; |
289 | u16 size; |
290 | |
291 | if (ar->bmi.done_sent) { |
292 | ath6kl_err(fmt: "bmi done sent already, cmd %d disallowed\n" , cid); |
293 | return -EACCES; |
294 | } |
295 | |
296 | size = sizeof(cid) + sizeof(addr); |
297 | if (size > ar->bmi.max_cmd_size) { |
298 | WARN_ON(1); |
299 | return -EINVAL; |
300 | } |
301 | memset(ar->bmi.cmd_buf, 0, size); |
302 | |
303 | ath6kl_dbg(mask: ATH6KL_DBG_BMI, fmt: "bmi set app start: addr: 0x%x\n" , addr); |
304 | |
305 | offset = 0; |
306 | memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); |
307 | offset += sizeof(cid); |
308 | memcpy(&(ar->bmi.cmd_buf[offset]), &addr, sizeof(addr)); |
309 | offset += sizeof(addr); |
310 | |
311 | ret = ath6kl_hif_bmi_write(ar, buf: ar->bmi.cmd_buf, len: offset); |
312 | if (ret) { |
313 | ath6kl_err(fmt: "Unable to write to the device: %d\n" , ret); |
314 | return ret; |
315 | } |
316 | |
317 | return 0; |
318 | } |
319 | |
320 | int ath6kl_bmi_reg_read(struct ath6kl *ar, u32 addr, u32 *param) |
321 | { |
322 | u32 cid = BMI_READ_SOC_REGISTER; |
323 | int ret; |
324 | u32 offset; |
325 | u16 size; |
326 | |
327 | if (ar->bmi.done_sent) { |
328 | ath6kl_err(fmt: "bmi done sent already, cmd %d disallowed\n" , cid); |
329 | return -EACCES; |
330 | } |
331 | |
332 | size = sizeof(cid) + sizeof(addr); |
333 | if (size > ar->bmi.max_cmd_size) { |
334 | WARN_ON(1); |
335 | return -EINVAL; |
336 | } |
337 | memset(ar->bmi.cmd_buf, 0, size); |
338 | |
339 | ath6kl_dbg(mask: ATH6KL_DBG_BMI, fmt: "bmi read SOC reg: addr: 0x%x\n" , addr); |
340 | |
341 | offset = 0; |
342 | memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); |
343 | offset += sizeof(cid); |
344 | memcpy(&(ar->bmi.cmd_buf[offset]), &addr, sizeof(addr)); |
345 | offset += sizeof(addr); |
346 | |
347 | ret = ath6kl_hif_bmi_write(ar, buf: ar->bmi.cmd_buf, len: offset); |
348 | if (ret) { |
349 | ath6kl_err(fmt: "Unable to write to the device: %d\n" , ret); |
350 | return ret; |
351 | } |
352 | |
353 | ret = ath6kl_hif_bmi_read(ar, buf: ar->bmi.cmd_buf, len: sizeof(*param)); |
354 | if (ret) { |
355 | ath6kl_err(fmt: "Unable to read from the device: %d\n" , ret); |
356 | return ret; |
357 | } |
358 | memcpy(param, ar->bmi.cmd_buf, sizeof(*param)); |
359 | |
360 | return 0; |
361 | } |
362 | |
363 | int ath6kl_bmi_reg_write(struct ath6kl *ar, u32 addr, u32 param) |
364 | { |
365 | u32 cid = BMI_WRITE_SOC_REGISTER; |
366 | int ret; |
367 | u32 offset; |
368 | u16 size; |
369 | |
370 | if (ar->bmi.done_sent) { |
371 | ath6kl_err(fmt: "bmi done sent already, cmd %d disallowed\n" , cid); |
372 | return -EACCES; |
373 | } |
374 | |
375 | size = sizeof(cid) + sizeof(addr) + sizeof(param); |
376 | if (size > ar->bmi.max_cmd_size) { |
377 | WARN_ON(1); |
378 | return -EINVAL; |
379 | } |
380 | memset(ar->bmi.cmd_buf, 0, size); |
381 | |
382 | ath6kl_dbg(mask: ATH6KL_DBG_BMI, |
383 | fmt: "bmi write SOC reg: addr: 0x%x, param: %d\n" , |
384 | addr, param); |
385 | |
386 | offset = 0; |
387 | memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); |
388 | offset += sizeof(cid); |
389 | memcpy(&(ar->bmi.cmd_buf[offset]), &addr, sizeof(addr)); |
390 | offset += sizeof(addr); |
391 | memcpy(&(ar->bmi.cmd_buf[offset]), ¶m, sizeof(param)); |
392 | offset += sizeof(param); |
393 | |
394 | ret = ath6kl_hif_bmi_write(ar, buf: ar->bmi.cmd_buf, len: offset); |
395 | if (ret) { |
396 | ath6kl_err(fmt: "Unable to write to the device: %d\n" , ret); |
397 | return ret; |
398 | } |
399 | |
400 | return 0; |
401 | } |
402 | |
403 | int ath6kl_bmi_lz_data(struct ath6kl *ar, u8 *buf, u32 len) |
404 | { |
405 | u32 cid = BMI_LZ_DATA; |
406 | int ret; |
407 | u32 offset; |
408 | u32 len_remain, tx_len; |
409 | const u32 = sizeof(cid) + sizeof(len); |
410 | u16 size; |
411 | |
412 | if (ar->bmi.done_sent) { |
413 | ath6kl_err(fmt: "bmi done sent already, cmd %d disallowed\n" , cid); |
414 | return -EACCES; |
415 | } |
416 | |
417 | size = ar->bmi.max_data_size + header; |
418 | if (size > ar->bmi.max_cmd_size) { |
419 | WARN_ON(1); |
420 | return -EINVAL; |
421 | } |
422 | memset(ar->bmi.cmd_buf, 0, size); |
423 | |
424 | ath6kl_dbg(mask: ATH6KL_DBG_BMI, fmt: "bmi send LZ data: len: %d)\n" , |
425 | len); |
426 | |
427 | len_remain = len; |
428 | while (len_remain) { |
429 | tx_len = (len_remain < (ar->bmi.max_data_size - header)) ? |
430 | len_remain : (ar->bmi.max_data_size - header); |
431 | |
432 | offset = 0; |
433 | memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); |
434 | offset += sizeof(cid); |
435 | memcpy(&(ar->bmi.cmd_buf[offset]), &tx_len, sizeof(tx_len)); |
436 | offset += sizeof(tx_len); |
437 | memcpy(&(ar->bmi.cmd_buf[offset]), &buf[len - len_remain], |
438 | tx_len); |
439 | offset += tx_len; |
440 | |
441 | ret = ath6kl_hif_bmi_write(ar, buf: ar->bmi.cmd_buf, len: offset); |
442 | if (ret) { |
443 | ath6kl_err(fmt: "Unable to write to the device: %d\n" , |
444 | ret); |
445 | return ret; |
446 | } |
447 | |
448 | len_remain -= tx_len; |
449 | } |
450 | |
451 | return 0; |
452 | } |
453 | |
454 | int ath6kl_bmi_lz_stream_start(struct ath6kl *ar, u32 addr) |
455 | { |
456 | u32 cid = BMI_LZ_STREAM_START; |
457 | int ret; |
458 | u32 offset; |
459 | u16 size; |
460 | |
461 | if (ar->bmi.done_sent) { |
462 | ath6kl_err(fmt: "bmi done sent already, cmd %d disallowed\n" , cid); |
463 | return -EACCES; |
464 | } |
465 | |
466 | size = sizeof(cid) + sizeof(addr); |
467 | if (size > ar->bmi.max_cmd_size) { |
468 | WARN_ON(1); |
469 | return -EINVAL; |
470 | } |
471 | memset(ar->bmi.cmd_buf, 0, size); |
472 | |
473 | ath6kl_dbg(mask: ATH6KL_DBG_BMI, |
474 | fmt: "bmi LZ stream start: addr: 0x%x)\n" , |
475 | addr); |
476 | |
477 | offset = 0; |
478 | memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); |
479 | offset += sizeof(cid); |
480 | memcpy(&(ar->bmi.cmd_buf[offset]), &addr, sizeof(addr)); |
481 | offset += sizeof(addr); |
482 | |
483 | ret = ath6kl_hif_bmi_write(ar, buf: ar->bmi.cmd_buf, len: offset); |
484 | if (ret) { |
485 | ath6kl_err(fmt: "Unable to start LZ stream to the device: %d\n" , |
486 | ret); |
487 | return ret; |
488 | } |
489 | |
490 | return 0; |
491 | } |
492 | |
493 | int ath6kl_bmi_fast_download(struct ath6kl *ar, u32 addr, u8 *buf, u32 len) |
494 | { |
495 | int ret; |
496 | u32 last_word = 0; |
497 | u32 last_word_offset = len & ~0x3; |
498 | u32 unaligned_bytes = len & 0x3; |
499 | |
500 | ret = ath6kl_bmi_lz_stream_start(ar, addr); |
501 | if (ret) |
502 | return ret; |
503 | |
504 | if (unaligned_bytes) { |
505 | /* copy the last word into a zero padded buffer */ |
506 | memcpy(&last_word, &buf[last_word_offset], unaligned_bytes); |
507 | } |
508 | |
509 | ret = ath6kl_bmi_lz_data(ar, buf, len: last_word_offset); |
510 | if (ret) |
511 | return ret; |
512 | |
513 | if (unaligned_bytes) |
514 | ret = ath6kl_bmi_lz_data(ar, buf: (u8 *)&last_word, len: 4); |
515 | |
516 | if (!ret) { |
517 | /* Close compressed stream and open a new (fake) one. |
518 | * This serves mainly to flush Target caches. */ |
519 | ret = ath6kl_bmi_lz_stream_start(ar, addr: 0x00); |
520 | } |
521 | return ret; |
522 | } |
523 | |
524 | void ath6kl_bmi_reset(struct ath6kl *ar) |
525 | { |
526 | ar->bmi.done_sent = false; |
527 | } |
528 | |
529 | int ath6kl_bmi_init(struct ath6kl *ar) |
530 | { |
531 | if (WARN_ON(ar->bmi.max_data_size == 0)) |
532 | return -EINVAL; |
533 | |
534 | /* cmd + addr + len + data_size */ |
535 | ar->bmi.max_cmd_size = ar->bmi.max_data_size + (sizeof(u32) * 3); |
536 | |
537 | ar->bmi.cmd_buf = kzalloc(size: ar->bmi.max_cmd_size, GFP_KERNEL); |
538 | if (!ar->bmi.cmd_buf) |
539 | return -ENOMEM; |
540 | |
541 | return 0; |
542 | } |
543 | |
544 | void ath6kl_bmi_cleanup(struct ath6kl *ar) |
545 | { |
546 | kfree(objp: ar->bmi.cmd_buf); |
547 | ar->bmi.cmd_buf = NULL; |
548 | } |
549 | |