Commit | Line | Data |
---|---|---|
fb9987d0 | 1 | /* |
5b68138e | 2 | * Copyright (c) 2010-2011 Atheros Communications Inc. |
fb9987d0 S |
3 | * |
4 | * Permission to use, copy, modify, and/or distribute this software for any | |
5 | * purpose with or without fee is hereby granted, provided that the above | |
6 | * copyright notice and this permission notice appear in all copies. | |
7 | * | |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
15 | */ | |
16 | ||
516304b0 JP |
17 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
18 | ||
fb9987d0 S |
19 | #include "htc.h" |
20 | ||
21 | static int htc_issue_send(struct htc_target *target, struct sk_buff* skb, | |
40dc9e4b SM |
22 | u16 len, u8 flags, u8 epid) |
23 | ||
fb9987d0 S |
24 | { |
25 | struct htc_frame_hdr *hdr; | |
26 | struct htc_endpoint *endpoint = &target->endpoint[epid]; | |
27 | int status; | |
28 | ||
29 | hdr = (struct htc_frame_hdr *) | |
30 | skb_push(skb, sizeof(struct htc_frame_hdr)); | |
31 | hdr->endpoint_id = epid; | |
32 | hdr->flags = flags; | |
33 | hdr->payload_len = cpu_to_be16(len); | |
34 | ||
40dc9e4b SM |
35 | status = target->hif->send(target->hif_dev, endpoint->ul_pipeid, skb); |
36 | ||
fb9987d0 S |
37 | return status; |
38 | } | |
39 | ||
40 | static struct htc_endpoint *get_next_avail_ep(struct htc_endpoint *endpoint) | |
41 | { | |
42 | enum htc_endpoint_id avail_epid; | |
43 | ||
8116daf2 | 44 | for (avail_epid = (ENDPOINT_MAX - 1); avail_epid > ENDPOINT0; avail_epid--) |
fb9987d0 S |
45 | if (endpoint[avail_epid].service_id == 0) |
46 | return &endpoint[avail_epid]; | |
47 | return NULL; | |
48 | } | |
49 | ||
50 | static u8 service_to_ulpipe(u16 service_id) | |
51 | { | |
52 | switch (service_id) { | |
53 | case WMI_CONTROL_SVC: | |
54 | return 4; | |
55 | case WMI_BEACON_SVC: | |
56 | case WMI_CAB_SVC: | |
57 | case WMI_UAPSD_SVC: | |
58 | case WMI_MGMT_SVC: | |
59 | case WMI_DATA_VO_SVC: | |
60 | case WMI_DATA_VI_SVC: | |
61 | case WMI_DATA_BE_SVC: | |
62 | case WMI_DATA_BK_SVC: | |
63 | return 1; | |
64 | default: | |
65 | return 0; | |
66 | } | |
67 | } | |
68 | ||
69 | static u8 service_to_dlpipe(u16 service_id) | |
70 | { | |
71 | switch (service_id) { | |
72 | case WMI_CONTROL_SVC: | |
73 | return 3; | |
74 | case WMI_BEACON_SVC: | |
75 | case WMI_CAB_SVC: | |
76 | case WMI_UAPSD_SVC: | |
77 | case WMI_MGMT_SVC: | |
78 | case WMI_DATA_VO_SVC: | |
79 | case WMI_DATA_VI_SVC: | |
80 | case WMI_DATA_BE_SVC: | |
81 | case WMI_DATA_BK_SVC: | |
82 | return 2; | |
83 | default: | |
84 | return 0; | |
85 | } | |
86 | } | |
87 | ||
88 | static void htc_process_target_rdy(struct htc_target *target, | |
89 | void *buf) | |
90 | { | |
91 | struct htc_endpoint *endpoint; | |
92 | struct htc_ready_msg *htc_ready_msg = (struct htc_ready_msg *) buf; | |
93 | ||
fb9987d0 S |
94 | target->credit_size = be16_to_cpu(htc_ready_msg->credit_size); |
95 | ||
96 | endpoint = &target->endpoint[ENDPOINT0]; | |
97 | endpoint->service_id = HTC_CTRL_RSVD_SVC; | |
98 | endpoint->max_msglen = HTC_MAX_CONTROL_MESSAGE_LENGTH; | |
d8c49ffb | 99 | atomic_inc(&target->tgt_ready); |
fb9987d0 S |
100 | complete(&target->target_wait); |
101 | } | |
102 | ||
103 | static void htc_process_conn_rsp(struct htc_target *target, | |
104 | struct htc_frame_hdr *htc_hdr) | |
105 | { | |
106 | struct htc_conn_svc_rspmsg *svc_rspmsg; | |
107 | struct htc_endpoint *endpoint, *tmp_endpoint = NULL; | |
108 | u16 service_id; | |
109 | u16 max_msglen; | |
110 | enum htc_endpoint_id epid, tepid; | |
111 | ||
112 | svc_rspmsg = (struct htc_conn_svc_rspmsg *) | |
113 | ((void *) htc_hdr + sizeof(struct htc_frame_hdr)); | |
114 | ||
115 | if (svc_rspmsg->status == HTC_SERVICE_SUCCESS) { | |
116 | epid = svc_rspmsg->endpoint_id; | |
117 | service_id = be16_to_cpu(svc_rspmsg->service_id); | |
118 | max_msglen = be16_to_cpu(svc_rspmsg->max_msg_len); | |
119 | endpoint = &target->endpoint[epid]; | |
120 | ||
8116daf2 | 121 | for (tepid = (ENDPOINT_MAX - 1); tepid > ENDPOINT0; tepid--) { |
fb9987d0 S |
122 | tmp_endpoint = &target->endpoint[tepid]; |
123 | if (tmp_endpoint->service_id == service_id) { | |
124 | tmp_endpoint->service_id = 0; | |
125 | break; | |
126 | } | |
127 | } | |
128 | ||
8116daf2 | 129 | if (tepid == ENDPOINT0) |
fb9987d0 S |
130 | return; |
131 | ||
132 | endpoint->service_id = service_id; | |
133 | endpoint->max_txqdepth = tmp_endpoint->max_txqdepth; | |
134 | endpoint->ep_callbacks = tmp_endpoint->ep_callbacks; | |
135 | endpoint->ul_pipeid = tmp_endpoint->ul_pipeid; | |
136 | endpoint->dl_pipeid = tmp_endpoint->dl_pipeid; | |
137 | endpoint->max_msglen = max_msglen; | |
138 | target->conn_rsp_epid = epid; | |
139 | complete(&target->cmd_wait); | |
140 | } else { | |
141 | target->conn_rsp_epid = ENDPOINT_UNUSED; | |
142 | } | |
143 | } | |
144 | ||
145 | static int htc_config_pipe_credits(struct htc_target *target) | |
146 | { | |
147 | struct sk_buff *skb; | |
148 | struct htc_config_pipe_msg *cp_msg; | |
149 | int ret, time_left; | |
150 | ||
0fa35a58 | 151 | skb = alloc_skb(50 + sizeof(struct htc_frame_hdr), GFP_ATOMIC); |
fb9987d0 S |
152 | if (!skb) { |
153 | dev_err(target->dev, "failed to allocate send buffer\n"); | |
154 | return -ENOMEM; | |
155 | } | |
156 | skb_reserve(skb, sizeof(struct htc_frame_hdr)); | |
157 | ||
158 | cp_msg = (struct htc_config_pipe_msg *) | |
159 | skb_put(skb, sizeof(struct htc_config_pipe_msg)); | |
160 | ||
161 | cp_msg->message_id = cpu_to_be16(HTC_MSG_CONFIG_PIPE_ID); | |
162 | cp_msg->pipe_id = USB_WLAN_TX_PIPE; | |
6267dc70 | 163 | cp_msg->credits = target->credits; |
fb9987d0 S |
164 | |
165 | target->htc_flags |= HTC_OP_CONFIG_PIPE_CREDITS; | |
166 | ||
40dc9e4b | 167 | ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0); |
fb9987d0 S |
168 | if (ret) |
169 | goto err; | |
170 | ||
171 | time_left = wait_for_completion_timeout(&target->cmd_wait, HZ); | |
172 | if (!time_left) { | |
173 | dev_err(target->dev, "HTC credit config timeout\n"); | |
174 | return -ETIMEDOUT; | |
175 | } | |
176 | ||
177 | return 0; | |
178 | err: | |
0fa35a58 | 179 | kfree_skb(skb); |
fb9987d0 S |
180 | return -EINVAL; |
181 | } | |
182 | ||
183 | static int htc_setup_complete(struct htc_target *target) | |
184 | { | |
185 | struct sk_buff *skb; | |
186 | struct htc_comp_msg *comp_msg; | |
187 | int ret = 0, time_left; | |
188 | ||
0fa35a58 | 189 | skb = alloc_skb(50 + sizeof(struct htc_frame_hdr), GFP_ATOMIC); |
fb9987d0 S |
190 | if (!skb) { |
191 | dev_err(target->dev, "failed to allocate send buffer\n"); | |
192 | return -ENOMEM; | |
193 | } | |
194 | skb_reserve(skb, sizeof(struct htc_frame_hdr)); | |
195 | ||
196 | comp_msg = (struct htc_comp_msg *) | |
197 | skb_put(skb, sizeof(struct htc_comp_msg)); | |
198 | comp_msg->msg_id = cpu_to_be16(HTC_MSG_SETUP_COMPLETE_ID); | |
199 | ||
200 | target->htc_flags |= HTC_OP_START_WAIT; | |
201 | ||
40dc9e4b | 202 | ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0); |
fb9987d0 S |
203 | if (ret) |
204 | goto err; | |
205 | ||
206 | time_left = wait_for_completion_timeout(&target->cmd_wait, HZ); | |
207 | if (!time_left) { | |
208 | dev_err(target->dev, "HTC start timeout\n"); | |
209 | return -ETIMEDOUT; | |
210 | } | |
211 | ||
212 | return 0; | |
213 | ||
214 | err: | |
0fa35a58 | 215 | kfree_skb(skb); |
fb9987d0 S |
216 | return -EINVAL; |
217 | } | |
218 | ||
219 | /* HTC APIs */ | |
220 | ||
221 | int htc_init(struct htc_target *target) | |
222 | { | |
223 | int ret; | |
224 | ||
225 | ret = htc_config_pipe_credits(target); | |
226 | if (ret) | |
227 | return ret; | |
228 | ||
229 | return htc_setup_complete(target); | |
230 | } | |
231 | ||
232 | int htc_connect_service(struct htc_target *target, | |
233 | struct htc_service_connreq *service_connreq, | |
234 | enum htc_endpoint_id *conn_rsp_epid) | |
235 | { | |
236 | struct sk_buff *skb; | |
237 | struct htc_endpoint *endpoint; | |
238 | struct htc_conn_svc_msg *conn_msg; | |
239 | int ret, time_left; | |
240 | ||
241 | /* Find an available endpoint */ | |
242 | endpoint = get_next_avail_ep(target->endpoint); | |
243 | if (!endpoint) { | |
244 | dev_err(target->dev, "Endpoint is not available for" | |
245 | "service %d\n", service_connreq->service_id); | |
246 | return -EINVAL; | |
247 | } | |
248 | ||
249 | endpoint->service_id = service_connreq->service_id; | |
250 | endpoint->max_txqdepth = service_connreq->max_send_qdepth; | |
251 | endpoint->ul_pipeid = service_to_ulpipe(service_connreq->service_id); | |
252 | endpoint->dl_pipeid = service_to_dlpipe(service_connreq->service_id); | |
253 | endpoint->ep_callbacks = service_connreq->ep_callbacks; | |
254 | ||
0fa35a58 ML |
255 | skb = alloc_skb(sizeof(struct htc_conn_svc_msg) + |
256 | sizeof(struct htc_frame_hdr), GFP_ATOMIC); | |
fb9987d0 S |
257 | if (!skb) { |
258 | dev_err(target->dev, "Failed to allocate buf to send" | |
259 | "service connect req\n"); | |
260 | return -ENOMEM; | |
261 | } | |
262 | ||
263 | skb_reserve(skb, sizeof(struct htc_frame_hdr)); | |
264 | ||
265 | conn_msg = (struct htc_conn_svc_msg *) | |
266 | skb_put(skb, sizeof(struct htc_conn_svc_msg)); | |
267 | conn_msg->service_id = cpu_to_be16(service_connreq->service_id); | |
268 | conn_msg->msg_id = cpu_to_be16(HTC_MSG_CONNECT_SERVICE_ID); | |
269 | conn_msg->con_flags = cpu_to_be16(service_connreq->con_flags); | |
270 | conn_msg->dl_pipeid = endpoint->dl_pipeid; | |
271 | conn_msg->ul_pipeid = endpoint->ul_pipeid; | |
272 | ||
40dc9e4b | 273 | ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0); |
fb9987d0 S |
274 | if (ret) |
275 | goto err; | |
276 | ||
277 | time_left = wait_for_completion_timeout(&target->cmd_wait, HZ); | |
278 | if (!time_left) { | |
279 | dev_err(target->dev, "Service connection timeout for: %d\n", | |
280 | service_connreq->service_id); | |
281 | return -ETIMEDOUT; | |
282 | } | |
283 | ||
284 | *conn_rsp_epid = target->conn_rsp_epid; | |
285 | return 0; | |
286 | err: | |
0fa35a58 | 287 | kfree_skb(skb); |
fb9987d0 S |
288 | return ret; |
289 | } | |
290 | ||
d67ee533 | 291 | int htc_send(struct htc_target *target, struct sk_buff *skb) |
fb9987d0 | 292 | { |
d67ee533 SM |
293 | struct ath9k_htc_tx_ctl *tx_ctl; |
294 | ||
295 | tx_ctl = HTC_SKB_CB(skb); | |
296 | return htc_issue_send(target, skb, skb->len, 0, tx_ctl->epid); | |
fb9987d0 S |
297 | } |
298 | ||
d67ee533 SM |
299 | int htc_send_epid(struct htc_target *target, struct sk_buff *skb, |
300 | enum htc_endpoint_id epid) | |
fb9987d0 | 301 | { |
40dc9e4b | 302 | return htc_issue_send(target, skb, skb->len, 0, epid); |
fb9987d0 | 303 | } |
fb9987d0 | 304 | |
fb9987d0 S |
305 | void htc_stop(struct htc_target *target) |
306 | { | |
e1fe7c38 | 307 | target->hif->stop(target->hif_dev); |
fb9987d0 S |
308 | } |
309 | ||
310 | void htc_start(struct htc_target *target) | |
311 | { | |
e1fe7c38 | 312 | target->hif->start(target->hif_dev); |
fb9987d0 | 313 | } |
fb9987d0 | 314 | |
84c9e164 SM |
315 | void htc_sta_drain(struct htc_target *target, u8 idx) |
316 | { | |
317 | target->hif->sta_drain(target->hif_dev, idx); | |
fb9987d0 S |
318 | } |
319 | ||
320 | void ath9k_htc_txcompletion_cb(struct htc_target *htc_handle, | |
321 | struct sk_buff *skb, bool txok) | |
322 | { | |
323 | struct htc_endpoint *endpoint; | |
0fa35a58 | 324 | struct htc_frame_hdr *htc_hdr = NULL; |
fb9987d0 S |
325 | |
326 | if (htc_handle->htc_flags & HTC_OP_CONFIG_PIPE_CREDITS) { | |
327 | complete(&htc_handle->cmd_wait); | |
328 | htc_handle->htc_flags &= ~HTC_OP_CONFIG_PIPE_CREDITS; | |
f984d94c | 329 | goto ret; |
fb9987d0 S |
330 | } |
331 | ||
332 | if (htc_handle->htc_flags & HTC_OP_START_WAIT) { | |
333 | complete(&htc_handle->cmd_wait); | |
334 | htc_handle->htc_flags &= ~HTC_OP_START_WAIT; | |
f984d94c | 335 | goto ret; |
fb9987d0 S |
336 | } |
337 | ||
338 | if (skb) { | |
339 | htc_hdr = (struct htc_frame_hdr *) skb->data; | |
340 | endpoint = &htc_handle->endpoint[htc_hdr->endpoint_id]; | |
341 | skb_pull(skb, sizeof(struct htc_frame_hdr)); | |
342 | ||
343 | if (endpoint->ep_callbacks.tx) { | |
f6689072 S |
344 | endpoint->ep_callbacks.tx(endpoint->ep_callbacks.priv, |
345 | skb, htc_hdr->endpoint_id, | |
346 | txok); | |
0981c3b2 SM |
347 | } else { |
348 | kfree_skb(skb); | |
fb9987d0 S |
349 | } |
350 | } | |
f984d94c S |
351 | |
352 | return; | |
353 | ret: | |
811c69e6 | 354 | kfree_skb(skb); |
fb9987d0 S |
355 | } |
356 | ||
482b30b6 OR |
357 | static void ath9k_htc_fw_panic_report(struct htc_target *htc_handle, |
358 | struct sk_buff *skb) | |
359 | { | |
360 | uint32_t *pattern = (uint32_t *)skb->data; | |
361 | ||
362 | switch (*pattern) { | |
363 | case 0x33221199: | |
364 | { | |
365 | struct htc_panic_bad_vaddr *htc_panic; | |
366 | htc_panic = (struct htc_panic_bad_vaddr *) skb->data; | |
367 | dev_err(htc_handle->dev, "ath: firmware panic! " | |
368 | "exccause: 0x%08x; pc: 0x%08x; badvaddr: 0x%08x.\n", | |
369 | htc_panic->exccause, htc_panic->pc, | |
370 | htc_panic->badvaddr); | |
371 | break; | |
372 | } | |
373 | case 0x33221299: | |
374 | { | |
375 | struct htc_panic_bad_epid *htc_panic; | |
376 | htc_panic = (struct htc_panic_bad_epid *) skb->data; | |
377 | dev_err(htc_handle->dev, "ath: firmware panic! " | |
378 | "bad epid: 0x%08x\n", htc_panic->epid); | |
379 | break; | |
380 | } | |
381 | default: | |
382 | dev_err(htc_handle->dev, "ath: uknown panic pattern!\n"); | |
383 | break; | |
384 | } | |
385 | } | |
386 | ||
fb9987d0 S |
387 | /* |
388 | * HTC Messages are handled directly here and the obtained SKB | |
389 | * is freed. | |
390 | * | |
25985edc | 391 | * Service messages (Data, WMI) passed to the corresponding |
fb9987d0 S |
392 | * endpoint RX handlers, which have to free the SKB. |
393 | */ | |
394 | void ath9k_htc_rx_msg(struct htc_target *htc_handle, | |
395 | struct sk_buff *skb, u32 len, u8 pipe_id) | |
396 | { | |
397 | struct htc_frame_hdr *htc_hdr; | |
398 | enum htc_endpoint_id epid; | |
399 | struct htc_endpoint *endpoint; | |
7f1f5a00 | 400 | __be16 *msg_id; |
fb9987d0 S |
401 | |
402 | if (!htc_handle || !skb) | |
403 | return; | |
404 | ||
405 | htc_hdr = (struct htc_frame_hdr *) skb->data; | |
406 | epid = htc_hdr->endpoint_id; | |
407 | ||
482b30b6 OR |
408 | if (epid == 0x99) { |
409 | ath9k_htc_fw_panic_report(htc_handle, skb); | |
410 | kfree_skb(skb); | |
411 | return; | |
412 | } | |
413 | ||
fb9987d0 | 414 | if (epid >= ENDPOINT_MAX) { |
e6c6d33c ML |
415 | if (pipe_id != USB_REG_IN_PIPE) |
416 | dev_kfree_skb_any(skb); | |
417 | else | |
418 | kfree_skb(skb); | |
fb9987d0 S |
419 | return; |
420 | } | |
421 | ||
422 | if (epid == ENDPOINT0) { | |
423 | ||
424 | /* Handle trailer */ | |
425 | if (htc_hdr->flags & HTC_FLAGS_RECV_TRAILER) { | |
7f1f5a00 | 426 | if (be32_to_cpu(*(__be32 *) skb->data) == 0x00C60000) |
fb9987d0 | 427 | /* Move past the Watchdog pattern */ |
d5a4c5e3 | 428 | htc_hdr = (struct htc_frame_hdr *)(skb->data + 4); |
fb9987d0 S |
429 | } |
430 | ||
431 | /* Get the message ID */ | |
7f1f5a00 S |
432 | msg_id = (__be16 *) ((void *) htc_hdr + |
433 | sizeof(struct htc_frame_hdr)); | |
fb9987d0 S |
434 | |
435 | /* Now process HTC messages */ | |
436 | switch (be16_to_cpu(*msg_id)) { | |
437 | case HTC_MSG_READY_ID: | |
438 | htc_process_target_rdy(htc_handle, htc_hdr); | |
439 | break; | |
440 | case HTC_MSG_CONNECT_SERVICE_RESPONSE_ID: | |
441 | htc_process_conn_rsp(htc_handle, htc_hdr); | |
442 | break; | |
443 | default: | |
444 | break; | |
445 | } | |
446 | ||
e6c6d33c | 447 | kfree_skb(skb); |
fb9987d0 S |
448 | |
449 | } else { | |
450 | if (htc_hdr->flags & HTC_FLAGS_RECV_TRAILER) | |
451 | skb_trim(skb, len - htc_hdr->control[0]); | |
452 | ||
453 | skb_pull(skb, sizeof(struct htc_frame_hdr)); | |
454 | ||
455 | endpoint = &htc_handle->endpoint[epid]; | |
456 | if (endpoint->ep_callbacks.rx) | |
457 | endpoint->ep_callbacks.rx(endpoint->ep_callbacks.priv, | |
458 | skb, epid); | |
459 | } | |
460 | } | |
461 | ||
47fce026 SM |
462 | struct htc_target *ath9k_htc_hw_alloc(void *hif_handle, |
463 | struct ath9k_htc_hif *hif, | |
464 | struct device *dev) | |
fb9987d0 | 465 | { |
47fce026 | 466 | struct htc_endpoint *endpoint; |
fb9987d0 S |
467 | struct htc_target *target; |
468 | ||
469 | target = kzalloc(sizeof(struct htc_target), GFP_KERNEL); | |
e404decb | 470 | if (!target) |
47fce026 | 471 | return NULL; |
fb9987d0 S |
472 | |
473 | init_completion(&target->target_wait); | |
474 | init_completion(&target->cmd_wait); | |
475 | ||
476 | target->hif = hif; | |
477 | target->hif_dev = hif_handle; | |
478 | target->dev = dev; | |
479 | ||
480 | /* Assign control endpoint pipe IDs */ | |
481 | endpoint = &target->endpoint[ENDPOINT0]; | |
482 | endpoint->ul_pipeid = hif->control_ul_pipe; | |
483 | endpoint->dl_pipeid = hif->control_dl_pipe; | |
484 | ||
d8c49ffb SM |
485 | atomic_set(&target->tgt_ready, 0); |
486 | ||
47fce026 SM |
487 | return target; |
488 | } | |
489 | ||
490 | void ath9k_htc_hw_free(struct htc_target *htc) | |
491 | { | |
492 | kfree(htc); | |
493 | } | |
494 | ||
495 | int ath9k_htc_hw_init(struct htc_target *target, | |
fa6e15e0 RM |
496 | struct device *dev, u16 devid, |
497 | char *product, u32 drv_info) | |
47fce026 | 498 | { |
fa6e15e0 | 499 | if (ath9k_htc_probe_device(target, dev, devid, product, drv_info)) { |
516304b0 | 500 | pr_err("Failed to initialize the device\n"); |
fb9987d0 S |
501 | return -ENODEV; |
502 | } | |
503 | ||
504 | return 0; | |
505 | } | |
506 | ||
507 | void ath9k_htc_hw_deinit(struct htc_target *target, bool hot_unplug) | |
508 | { | |
509 | if (target) | |
510 | ath9k_htc_disconnect_device(target, hot_unplug); | |
511 | } |