1 | /* |
2 | * Copyright (c) 2007-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 | #include "hif.h" |
18 | |
19 | #include <linux/export.h> |
20 | |
21 | #include "core.h" |
22 | #include "target.h" |
23 | #include "hif-ops.h" |
24 | #include "debug.h" |
25 | #include "trace.h" |
26 | |
27 | #define MAILBOX_FOR_BLOCK_SIZE 1 |
28 | |
29 | #define ATH6KL_TIME_QUANTUM 10 /* in ms */ |
30 | |
31 | static int ath6kl_hif_cp_scat_dma_buf(struct hif_scatter_req *req, |
32 | bool from_dma) |
33 | { |
34 | u8 *buf; |
35 | int i; |
36 | |
37 | buf = req->virt_dma_buf; |
38 | |
39 | for (i = 0; i < req->scat_entries; i++) { |
40 | if (from_dma) |
41 | memcpy(req->scat_list[i].buf, buf, |
42 | req->scat_list[i].len); |
43 | else |
44 | memcpy(buf, req->scat_list[i].buf, |
45 | req->scat_list[i].len); |
46 | |
47 | buf += req->scat_list[i].len; |
48 | } |
49 | |
50 | return 0; |
51 | } |
52 | |
53 | int ath6kl_hif_rw_comp_handler(void *context, int status) |
54 | { |
55 | struct htc_packet *packet = context; |
56 | |
57 | ath6kl_dbg(mask: ATH6KL_DBG_HIF, fmt: "hif rw completion pkt 0x%p status %d\n" , |
58 | packet, status); |
59 | |
60 | packet->status = status; |
61 | packet->completion(packet->context, packet); |
62 | |
63 | return 0; |
64 | } |
65 | EXPORT_SYMBOL(ath6kl_hif_rw_comp_handler); |
66 | |
67 | #define REGISTER_DUMP_COUNT 60 |
68 | #define REGISTER_DUMP_LEN_MAX 60 |
69 | |
70 | static void ath6kl_hif_dump_fw_crash(struct ath6kl *ar) |
71 | { |
72 | __le32 regdump_val[REGISTER_DUMP_LEN_MAX]; |
73 | u32 i, address, regdump_addr = 0; |
74 | int ret; |
75 | |
76 | /* the reg dump pointer is copied to the host interest area */ |
77 | address = ath6kl_get_hi_item_addr(ar, HI_ITEM(hi_failure_state)); |
78 | address = TARG_VTOP(ar->target_type, address); |
79 | |
80 | /* read RAM location through diagnostic window */ |
81 | ret = ath6kl_diag_read32(ar, address, value: ®dump_addr); |
82 | |
83 | if (ret || !regdump_addr) { |
84 | ath6kl_warn(fmt: "failed to get ptr to register dump area: %d\n" , |
85 | ret); |
86 | return; |
87 | } |
88 | |
89 | ath6kl_dbg(mask: ATH6KL_DBG_IRQ, fmt: "register dump data address 0x%x\n" , |
90 | regdump_addr); |
91 | regdump_addr = TARG_VTOP(ar->target_type, regdump_addr); |
92 | |
93 | /* fetch register dump data */ |
94 | ret = ath6kl_diag_read(ar, address: regdump_addr, data: (u8 *)®dump_val[0], |
95 | REGISTER_DUMP_COUNT * (sizeof(u32))); |
96 | if (ret) { |
97 | ath6kl_warn(fmt: "failed to get register dump: %d\n" , ret); |
98 | return; |
99 | } |
100 | |
101 | ath6kl_info(fmt: "crash dump:\n" ); |
102 | ath6kl_info(fmt: "hw 0x%x fw %s\n" , ar->wiphy->hw_version, |
103 | ar->wiphy->fw_version); |
104 | |
105 | BUILD_BUG_ON(REGISTER_DUMP_COUNT % 4); |
106 | |
107 | for (i = 0; i < REGISTER_DUMP_COUNT; i += 4) { |
108 | ath6kl_info(fmt: "%d: 0x%8.8x 0x%8.8x 0x%8.8x 0x%8.8x\n" , |
109 | i, |
110 | le32_to_cpu(regdump_val[i]), |
111 | le32_to_cpu(regdump_val[i + 1]), |
112 | le32_to_cpu(regdump_val[i + 2]), |
113 | le32_to_cpu(regdump_val[i + 3])); |
114 | } |
115 | } |
116 | |
117 | static int ath6kl_hif_proc_dbg_intr(struct ath6kl_device *dev) |
118 | { |
119 | u32 dummy; |
120 | int ret; |
121 | |
122 | ath6kl_warn(fmt: "firmware crashed\n" ); |
123 | |
124 | /* |
125 | * read counter to clear the interrupt, the debug error interrupt is |
126 | * counter 0. |
127 | */ |
128 | ret = hif_read_write_sync(ar: dev->ar, COUNT_DEC_ADDRESS, |
129 | buf: (u8 *)&dummy, len: 4, HIF_RD_SYNC_BYTE_INC); |
130 | if (ret) |
131 | ath6kl_warn(fmt: "Failed to clear debug interrupt: %d\n" , ret); |
132 | |
133 | ath6kl_hif_dump_fw_crash(ar: dev->ar); |
134 | ath6kl_read_fwlogs(ar: dev->ar); |
135 | ath6kl_recovery_err_notify(ar: dev->ar, reason: ATH6KL_FW_ASSERT); |
136 | |
137 | return ret; |
138 | } |
139 | |
140 | /* mailbox recv message polling */ |
141 | int ath6kl_hif_poll_mboxmsg_rx(struct ath6kl_device *dev, u32 *lk_ahd, |
142 | int timeout) |
143 | { |
144 | struct ath6kl_irq_proc_registers *rg; |
145 | int status = 0, i; |
146 | u8 htc_mbox = 1 << HTC_MAILBOX; |
147 | |
148 | for (i = timeout / ATH6KL_TIME_QUANTUM; i > 0; i--) { |
149 | /* this is the standard HIF way, load the reg table */ |
150 | status = hif_read_write_sync(ar: dev->ar, HOST_INT_STATUS_ADDRESS, |
151 | buf: (u8 *) &dev->irq_proc_reg, |
152 | len: sizeof(dev->irq_proc_reg), |
153 | HIF_RD_SYNC_BYTE_INC); |
154 | |
155 | if (status) { |
156 | ath6kl_err(fmt: "failed to read reg table\n" ); |
157 | return status; |
158 | } |
159 | |
160 | /* check for MBOX data and valid lookahead */ |
161 | if (dev->irq_proc_reg.host_int_status & htc_mbox) { |
162 | if (dev->irq_proc_reg.rx_lkahd_valid & |
163 | htc_mbox) { |
164 | /* |
165 | * Mailbox has a message and the look ahead |
166 | * is valid. |
167 | */ |
168 | rg = &dev->irq_proc_reg; |
169 | *lk_ahd = |
170 | le32_to_cpu(rg->rx_lkahd[HTC_MAILBOX]); |
171 | break; |
172 | } |
173 | } |
174 | |
175 | /* delay a little */ |
176 | mdelay(ATH6KL_TIME_QUANTUM); |
177 | ath6kl_dbg(mask: ATH6KL_DBG_HIF, fmt: "hif retry mbox poll try %d\n" , i); |
178 | } |
179 | |
180 | if (i == 0) { |
181 | ath6kl_err(fmt: "timeout waiting for recv message\n" ); |
182 | status = -ETIME; |
183 | /* check if the target asserted */ |
184 | if (dev->irq_proc_reg.counter_int_status & |
185 | ATH6KL_TARGET_DEBUG_INTR_MASK) |
186 | /* |
187 | * Target failure handler will be called in case of |
188 | * an assert. |
189 | */ |
190 | ath6kl_hif_proc_dbg_intr(dev); |
191 | } |
192 | |
193 | return status; |
194 | } |
195 | |
196 | /* |
197 | * Disable packet reception (used in case the host runs out of buffers) |
198 | * using the interrupt enable registers through the host I/F |
199 | */ |
200 | int ath6kl_hif_rx_control(struct ath6kl_device *dev, bool enable_rx) |
201 | { |
202 | struct ath6kl_irq_enable_reg regs; |
203 | int status = 0; |
204 | |
205 | ath6kl_dbg(mask: ATH6KL_DBG_HIF, fmt: "hif rx %s\n" , |
206 | enable_rx ? "enable" : "disable" ); |
207 | |
208 | /* take the lock to protect interrupt enable shadows */ |
209 | spin_lock_bh(lock: &dev->lock); |
210 | |
211 | if (enable_rx) |
212 | dev->irq_en_reg.int_status_en |= |
213 | SM(INT_STATUS_ENABLE_MBOX_DATA, 0x01); |
214 | else |
215 | dev->irq_en_reg.int_status_en &= |
216 | ~SM(INT_STATUS_ENABLE_MBOX_DATA, 0x01); |
217 | |
218 | memcpy(®s, &dev->irq_en_reg, sizeof(regs)); |
219 | |
220 | spin_unlock_bh(lock: &dev->lock); |
221 | |
222 | status = hif_read_write_sync(ar: dev->ar, INT_STATUS_ENABLE_ADDRESS, |
223 | buf: ®s.int_status_en, |
224 | len: sizeof(struct ath6kl_irq_enable_reg), |
225 | HIF_WR_SYNC_BYTE_INC); |
226 | |
227 | return status; |
228 | } |
229 | |
230 | int ath6kl_hif_submit_scat_req(struct ath6kl_device *dev, |
231 | struct hif_scatter_req *scat_req, bool read) |
232 | { |
233 | int status = 0; |
234 | |
235 | if (read) { |
236 | scat_req->req = HIF_RD_SYNC_BLOCK_FIX; |
237 | scat_req->addr = dev->ar->mbox_info.htc_addr; |
238 | } else { |
239 | scat_req->req = HIF_WR_ASYNC_BLOCK_INC; |
240 | |
241 | scat_req->addr = |
242 | (scat_req->len > HIF_MBOX_WIDTH) ? |
243 | dev->ar->mbox_info.htc_ext_addr : |
244 | dev->ar->mbox_info.htc_addr; |
245 | } |
246 | |
247 | ath6kl_dbg(mask: ATH6KL_DBG_HIF, |
248 | fmt: "hif submit scatter request entries %d len %d mbox 0x%x %s %s\n" , |
249 | scat_req->scat_entries, scat_req->len, |
250 | scat_req->addr, !read ? "async" : "sync" , |
251 | (read) ? "rd" : "wr" ); |
252 | |
253 | if (!read && scat_req->virt_scat) { |
254 | status = ath6kl_hif_cp_scat_dma_buf(req: scat_req, from_dma: false); |
255 | if (status) { |
256 | scat_req->status = status; |
257 | scat_req->complete(dev->ar->htc_target, scat_req); |
258 | return 0; |
259 | } |
260 | } |
261 | |
262 | status = ath6kl_hif_scat_req_rw(ar: dev->ar, scat_req); |
263 | |
264 | if (read) { |
265 | /* in sync mode, we can touch the scatter request */ |
266 | scat_req->status = status; |
267 | if (!status && scat_req->virt_scat) |
268 | scat_req->status = |
269 | ath6kl_hif_cp_scat_dma_buf(req: scat_req, from_dma: true); |
270 | } |
271 | |
272 | return status; |
273 | } |
274 | |
275 | static int ath6kl_hif_proc_counter_intr(struct ath6kl_device *dev) |
276 | { |
277 | u8 counter_int_status; |
278 | |
279 | ath6kl_dbg(mask: ATH6KL_DBG_IRQ, fmt: "counter interrupt\n" ); |
280 | |
281 | counter_int_status = dev->irq_proc_reg.counter_int_status & |
282 | dev->irq_en_reg.cntr_int_status_en; |
283 | |
284 | ath6kl_dbg(mask: ATH6KL_DBG_IRQ, |
285 | fmt: "valid interrupt source(s) in COUNTER_INT_STATUS: 0x%x\n" , |
286 | counter_int_status); |
287 | |
288 | /* |
289 | * NOTE: other modules like GMBOX may use the counter interrupt for |
290 | * credit flow control on other counters, we only need to check for |
291 | * the debug assertion counter interrupt. |
292 | */ |
293 | if (counter_int_status & ATH6KL_TARGET_DEBUG_INTR_MASK) |
294 | return ath6kl_hif_proc_dbg_intr(dev); |
295 | |
296 | return 0; |
297 | } |
298 | |
299 | static int ath6kl_hif_proc_err_intr(struct ath6kl_device *dev) |
300 | { |
301 | int status; |
302 | u8 error_int_status; |
303 | u8 reg_buf[4]; |
304 | |
305 | ath6kl_dbg(mask: ATH6KL_DBG_IRQ, fmt: "error interrupt\n" ); |
306 | |
307 | error_int_status = dev->irq_proc_reg.error_int_status & 0x0F; |
308 | if (!error_int_status) { |
309 | WARN_ON(1); |
310 | return -EIO; |
311 | } |
312 | |
313 | ath6kl_dbg(mask: ATH6KL_DBG_IRQ, |
314 | fmt: "valid interrupt source(s) in ERROR_INT_STATUS: 0x%x\n" , |
315 | error_int_status); |
316 | |
317 | if (MS(ERROR_INT_STATUS_WAKEUP, error_int_status)) |
318 | ath6kl_dbg(mask: ATH6KL_DBG_IRQ, fmt: "error : wakeup\n" ); |
319 | |
320 | if (MS(ERROR_INT_STATUS_RX_UNDERFLOW, error_int_status)) |
321 | ath6kl_err(fmt: "rx underflow\n" ); |
322 | |
323 | if (MS(ERROR_INT_STATUS_TX_OVERFLOW, error_int_status)) |
324 | ath6kl_err(fmt: "tx overflow\n" ); |
325 | |
326 | /* Clear the interrupt */ |
327 | dev->irq_proc_reg.error_int_status &= ~error_int_status; |
328 | |
329 | /* set W1C value to clear the interrupt, this hits the register first */ |
330 | reg_buf[0] = error_int_status; |
331 | reg_buf[1] = 0; |
332 | reg_buf[2] = 0; |
333 | reg_buf[3] = 0; |
334 | |
335 | status = hif_read_write_sync(ar: dev->ar, ERROR_INT_STATUS_ADDRESS, |
336 | buf: reg_buf, len: 4, HIF_WR_SYNC_BYTE_FIX); |
337 | |
338 | WARN_ON(status); |
339 | |
340 | return status; |
341 | } |
342 | |
343 | static int ath6kl_hif_proc_cpu_intr(struct ath6kl_device *dev) |
344 | { |
345 | int status; |
346 | u8 cpu_int_status; |
347 | u8 reg_buf[4]; |
348 | |
349 | ath6kl_dbg(mask: ATH6KL_DBG_IRQ, fmt: "cpu interrupt\n" ); |
350 | |
351 | cpu_int_status = dev->irq_proc_reg.cpu_int_status & |
352 | dev->irq_en_reg.cpu_int_status_en; |
353 | if (!cpu_int_status) { |
354 | WARN_ON(1); |
355 | return -EIO; |
356 | } |
357 | |
358 | ath6kl_dbg(mask: ATH6KL_DBG_IRQ, |
359 | fmt: "valid interrupt source(s) in CPU_INT_STATUS: 0x%x\n" , |
360 | cpu_int_status); |
361 | |
362 | /* Clear the interrupt */ |
363 | dev->irq_proc_reg.cpu_int_status &= ~cpu_int_status; |
364 | |
365 | /* |
366 | * Set up the register transfer buffer to hit the register 4 times , |
367 | * this is done to make the access 4-byte aligned to mitigate issues |
368 | * with host bus interconnects that restrict bus transfer lengths to |
369 | * be a multiple of 4-bytes. |
370 | */ |
371 | |
372 | /* set W1C value to clear the interrupt, this hits the register first */ |
373 | reg_buf[0] = cpu_int_status; |
374 | /* the remaining are set to zero which have no-effect */ |
375 | reg_buf[1] = 0; |
376 | reg_buf[2] = 0; |
377 | reg_buf[3] = 0; |
378 | |
379 | status = hif_read_write_sync(ar: dev->ar, CPU_INT_STATUS_ADDRESS, |
380 | buf: reg_buf, len: 4, HIF_WR_SYNC_BYTE_FIX); |
381 | |
382 | WARN_ON(status); |
383 | |
384 | return status; |
385 | } |
386 | |
387 | /* process pending interrupts synchronously */ |
388 | static int proc_pending_irqs(struct ath6kl_device *dev, bool *done) |
389 | { |
390 | struct ath6kl_irq_proc_registers *rg; |
391 | int status = 0; |
392 | u8 host_int_status = 0; |
393 | u32 lk_ahd = 0; |
394 | u8 htc_mbox = 1 << HTC_MAILBOX; |
395 | |
396 | ath6kl_dbg(mask: ATH6KL_DBG_IRQ, fmt: "proc_pending_irqs: (dev: 0x%p)\n" , dev); |
397 | |
398 | /* |
399 | * NOTE: HIF implementation guarantees that the context of this |
400 | * call allows us to perform SYNCHRONOUS I/O, that is we can block, |
401 | * sleep or call any API that can block or switch thread/task |
402 | * contexts. This is a fully schedulable context. |
403 | */ |
404 | |
405 | /* |
406 | * Process pending intr only when int_status_en is clear, it may |
407 | * result in unnecessary bus transaction otherwise. Target may be |
408 | * unresponsive at the time. |
409 | */ |
410 | if (dev->irq_en_reg.int_status_en) { |
411 | /* |
412 | * Read the first 28 bytes of the HTC register table. This |
413 | * will yield us the value of different int status |
414 | * registers and the lookahead registers. |
415 | * |
416 | * length = sizeof(int_status) + sizeof(cpu_int_status) |
417 | * + sizeof(error_int_status) + |
418 | * sizeof(counter_int_status) + |
419 | * sizeof(mbox_frame) + sizeof(rx_lkahd_valid) |
420 | * + sizeof(hole) + sizeof(rx_lkahd) + |
421 | * sizeof(int_status_en) + |
422 | * sizeof(cpu_int_status_en) + |
423 | * sizeof(err_int_status_en) + |
424 | * sizeof(cntr_int_status_en); |
425 | */ |
426 | status = hif_read_write_sync(ar: dev->ar, HOST_INT_STATUS_ADDRESS, |
427 | buf: (u8 *) &dev->irq_proc_reg, |
428 | len: sizeof(dev->irq_proc_reg), |
429 | HIF_RD_SYNC_BYTE_INC); |
430 | if (status) |
431 | goto out; |
432 | |
433 | ath6kl_dump_registers(dev, irq_proc_reg: &dev->irq_proc_reg, |
434 | irq_en_reg: &dev->irq_en_reg); |
435 | trace_ath6kl_sdio_irq(buf: &dev->irq_en_reg, |
436 | buf_len: sizeof(dev->irq_en_reg)); |
437 | |
438 | /* Update only those registers that are enabled */ |
439 | host_int_status = dev->irq_proc_reg.host_int_status & |
440 | dev->irq_en_reg.int_status_en; |
441 | |
442 | /* Look at mbox status */ |
443 | if (host_int_status & htc_mbox) { |
444 | /* |
445 | * Mask out pending mbox value, we use "lookAhead as |
446 | * the real flag for mbox processing. |
447 | */ |
448 | host_int_status &= ~htc_mbox; |
449 | if (dev->irq_proc_reg.rx_lkahd_valid & |
450 | htc_mbox) { |
451 | rg = &dev->irq_proc_reg; |
452 | lk_ahd = le32_to_cpu(rg->rx_lkahd[HTC_MAILBOX]); |
453 | if (!lk_ahd) |
454 | ath6kl_err(fmt: "lookAhead is zero!\n" ); |
455 | } |
456 | } |
457 | } |
458 | |
459 | if (!host_int_status && !lk_ahd) { |
460 | *done = true; |
461 | goto out; |
462 | } |
463 | |
464 | if (lk_ahd) { |
465 | int fetched = 0; |
466 | |
467 | ath6kl_dbg(mask: ATH6KL_DBG_IRQ, |
468 | fmt: "pending mailbox msg, lk_ahd: 0x%X\n" , lk_ahd); |
469 | /* |
470 | * Mailbox Interrupt, the HTC layer may issue async |
471 | * requests to empty the mailbox. When emptying the recv |
472 | * mailbox we use the async handler above called from the |
473 | * completion routine of the callers read request. This can |
474 | * improve performance by reducing context switching when |
475 | * we rapidly pull packets. |
476 | */ |
477 | status = ath6kl_htc_rxmsg_pending_handler(target: dev->htc_cnxt, |
478 | msg_look_ahead: lk_ahd, n_pkts: &fetched); |
479 | if (status) |
480 | goto out; |
481 | |
482 | if (!fetched) |
483 | /* |
484 | * HTC could not pull any messages out due to lack |
485 | * of resources. |
486 | */ |
487 | dev->htc_cnxt->chk_irq_status_cnt = 0; |
488 | } |
489 | |
490 | /* now handle the rest of them */ |
491 | ath6kl_dbg(mask: ATH6KL_DBG_IRQ, |
492 | fmt: "valid interrupt source(s) for other interrupts: 0x%x\n" , |
493 | host_int_status); |
494 | |
495 | if (MS(HOST_INT_STATUS_CPU, host_int_status)) { |
496 | /* CPU Interrupt */ |
497 | status = ath6kl_hif_proc_cpu_intr(dev); |
498 | if (status) |
499 | goto out; |
500 | } |
501 | |
502 | if (MS(HOST_INT_STATUS_ERROR, host_int_status)) { |
503 | /* Error Interrupt */ |
504 | status = ath6kl_hif_proc_err_intr(dev); |
505 | if (status) |
506 | goto out; |
507 | } |
508 | |
509 | if (MS(HOST_INT_STATUS_COUNTER, host_int_status)) |
510 | /* Counter Interrupt */ |
511 | status = ath6kl_hif_proc_counter_intr(dev); |
512 | |
513 | out: |
514 | /* |
515 | * An optimization to bypass reading the IRQ status registers |
516 | * unecessarily which can re-wake the target, if upper layers |
517 | * determine that we are in a low-throughput mode, we can rely on |
518 | * taking another interrupt rather than re-checking the status |
519 | * registers which can re-wake the target. |
520 | * |
521 | * NOTE : for host interfaces that makes use of detecting pending |
522 | * mbox messages at hif can not use this optimization due to |
523 | * possible side effects, SPI requires the host to drain all |
524 | * messages from the mailbox before exiting the ISR routine. |
525 | */ |
526 | |
527 | ath6kl_dbg(mask: ATH6KL_DBG_IRQ, |
528 | fmt: "bypassing irq status re-check, forcing done\n" ); |
529 | |
530 | if (!dev->htc_cnxt->chk_irq_status_cnt) |
531 | *done = true; |
532 | |
533 | ath6kl_dbg(mask: ATH6KL_DBG_IRQ, |
534 | fmt: "proc_pending_irqs: (done:%d, status=%d\n" , *done, status); |
535 | |
536 | return status; |
537 | } |
538 | |
539 | /* interrupt handler, kicks off all interrupt processing */ |
540 | int ath6kl_hif_intr_bh_handler(struct ath6kl *ar) |
541 | { |
542 | struct ath6kl_device *dev = ar->htc_target->dev; |
543 | unsigned long timeout; |
544 | int status = 0; |
545 | bool done = false; |
546 | |
547 | /* |
548 | * Reset counter used to flag a re-scan of IRQ status registers on |
549 | * the target. |
550 | */ |
551 | dev->htc_cnxt->chk_irq_status_cnt = 0; |
552 | |
553 | /* |
554 | * IRQ processing is synchronous, interrupt status registers can be |
555 | * re-read. |
556 | */ |
557 | timeout = jiffies + msecs_to_jiffies(ATH6KL_HIF_COMMUNICATION_TIMEOUT); |
558 | while (time_before(jiffies, timeout) && !done) { |
559 | status = proc_pending_irqs(dev, done: &done); |
560 | if (status) |
561 | break; |
562 | } |
563 | |
564 | return status; |
565 | } |
566 | EXPORT_SYMBOL(ath6kl_hif_intr_bh_handler); |
567 | |
568 | static int ath6kl_hif_enable_intrs(struct ath6kl_device *dev) |
569 | { |
570 | struct ath6kl_irq_enable_reg regs; |
571 | int status; |
572 | |
573 | spin_lock_bh(lock: &dev->lock); |
574 | |
575 | /* Enable all but ATH6KL CPU interrupts */ |
576 | dev->irq_en_reg.int_status_en = |
577 | SM(INT_STATUS_ENABLE_ERROR, 0x01) | |
578 | SM(INT_STATUS_ENABLE_CPU, 0x01) | |
579 | SM(INT_STATUS_ENABLE_COUNTER, 0x01); |
580 | |
581 | /* |
582 | * NOTE: There are some cases where HIF can do detection of |
583 | * pending mbox messages which is disabled now. |
584 | */ |
585 | dev->irq_en_reg.int_status_en |= SM(INT_STATUS_ENABLE_MBOX_DATA, 0x01); |
586 | |
587 | /* Set up the CPU Interrupt status Register */ |
588 | dev->irq_en_reg.cpu_int_status_en = 0; |
589 | |
590 | /* Set up the Error Interrupt status Register */ |
591 | dev->irq_en_reg.err_int_status_en = |
592 | SM(ERROR_STATUS_ENABLE_RX_UNDERFLOW, 0x01) | |
593 | SM(ERROR_STATUS_ENABLE_TX_OVERFLOW, 0x1); |
594 | |
595 | /* |
596 | * Enable Counter interrupt status register to get fatal errors for |
597 | * debugging. |
598 | */ |
599 | dev->irq_en_reg.cntr_int_status_en = SM(COUNTER_INT_STATUS_ENABLE_BIT, |
600 | ATH6KL_TARGET_DEBUG_INTR_MASK); |
601 | memcpy(®s, &dev->irq_en_reg, sizeof(regs)); |
602 | |
603 | spin_unlock_bh(lock: &dev->lock); |
604 | |
605 | status = hif_read_write_sync(ar: dev->ar, INT_STATUS_ENABLE_ADDRESS, |
606 | buf: ®s.int_status_en, len: sizeof(regs), |
607 | HIF_WR_SYNC_BYTE_INC); |
608 | |
609 | if (status) |
610 | ath6kl_err(fmt: "failed to update interrupt ctl reg err: %d\n" , |
611 | status); |
612 | |
613 | return status; |
614 | } |
615 | |
616 | int ath6kl_hif_disable_intrs(struct ath6kl_device *dev) |
617 | { |
618 | struct ath6kl_irq_enable_reg regs; |
619 | |
620 | spin_lock_bh(lock: &dev->lock); |
621 | /* Disable all interrupts */ |
622 | dev->irq_en_reg.int_status_en = 0; |
623 | dev->irq_en_reg.cpu_int_status_en = 0; |
624 | dev->irq_en_reg.err_int_status_en = 0; |
625 | dev->irq_en_reg.cntr_int_status_en = 0; |
626 | memcpy(®s, &dev->irq_en_reg, sizeof(regs)); |
627 | spin_unlock_bh(lock: &dev->lock); |
628 | |
629 | return hif_read_write_sync(ar: dev->ar, INT_STATUS_ENABLE_ADDRESS, |
630 | buf: ®s.int_status_en, len: sizeof(regs), |
631 | HIF_WR_SYNC_BYTE_INC); |
632 | } |
633 | |
634 | /* enable device interrupts */ |
635 | int ath6kl_hif_unmask_intrs(struct ath6kl_device *dev) |
636 | { |
637 | int status = 0; |
638 | |
639 | /* |
640 | * Make sure interrupt are disabled before unmasking at the HIF |
641 | * layer. The rationale here is that between device insertion |
642 | * (where we clear the interrupts the first time) and when HTC |
643 | * is finally ready to handle interrupts, other software can perform |
644 | * target "soft" resets. The ATH6KL interrupt enables reset back to an |
645 | * "enabled" state when this happens. |
646 | */ |
647 | ath6kl_hif_disable_intrs(dev); |
648 | |
649 | /* unmask the host controller interrupts */ |
650 | ath6kl_hif_irq_enable(ar: dev->ar); |
651 | status = ath6kl_hif_enable_intrs(dev); |
652 | |
653 | return status; |
654 | } |
655 | |
656 | /* disable all device interrupts */ |
657 | int ath6kl_hif_mask_intrs(struct ath6kl_device *dev) |
658 | { |
659 | /* |
660 | * Mask the interrupt at the HIF layer to avoid any stray interrupt |
661 | * taken while we zero out our shadow registers in |
662 | * ath6kl_hif_disable_intrs(). |
663 | */ |
664 | ath6kl_hif_irq_disable(ar: dev->ar); |
665 | |
666 | return ath6kl_hif_disable_intrs(dev); |
667 | } |
668 | |
669 | int ath6kl_hif_setup(struct ath6kl_device *dev) |
670 | { |
671 | int status = 0; |
672 | |
673 | spin_lock_init(&dev->lock); |
674 | |
675 | /* |
676 | * NOTE: we actually get the block size of a mailbox other than 0, |
677 | * for SDIO the block size on mailbox 0 is artificially set to 1. |
678 | * So we use the block size that is set for the other 3 mailboxes. |
679 | */ |
680 | dev->htc_cnxt->block_sz = dev->ar->mbox_info.block_size; |
681 | |
682 | /* must be a power of 2 */ |
683 | if ((dev->htc_cnxt->block_sz & (dev->htc_cnxt->block_sz - 1)) != 0) { |
684 | WARN_ON(1); |
685 | status = -EINVAL; |
686 | goto fail_setup; |
687 | } |
688 | |
689 | /* assemble mask, used for padding to a block */ |
690 | dev->htc_cnxt->block_mask = dev->htc_cnxt->block_sz - 1; |
691 | |
692 | ath6kl_dbg(mask: ATH6KL_DBG_HIF, fmt: "hif block size %d mbox addr 0x%x\n" , |
693 | dev->htc_cnxt->block_sz, dev->ar->mbox_info.htc_addr); |
694 | |
695 | status = ath6kl_hif_disable_intrs(dev); |
696 | |
697 | fail_setup: |
698 | return status; |
699 | } |
700 | |