]>
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 | ||
d58ff351 | 29 | hdr = skb_push(skb, sizeof(struct htc_frame_hdr)); |
fb9987d0 S |
30 | hdr->endpoint_id = epid; |
31 | hdr->flags = flags; | |
32 | hdr->payload_len = cpu_to_be16(len); | |
b59ef5a7 | 33 | memset(hdr->control, 0, sizeof(hdr->control)); |
fb9987d0 | 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; | |
e4ff08a4 QH |
117 | if (epid < 0 || epid >= ENDPOINT_MAX) |
118 | return; | |
119 | ||
fb9987d0 S |
120 | service_id = be16_to_cpu(svc_rspmsg->service_id); |
121 | max_msglen = be16_to_cpu(svc_rspmsg->max_msg_len); | |
122 | endpoint = &target->endpoint[epid]; | |
123 | ||
8116daf2 | 124 | for (tepid = (ENDPOINT_MAX - 1); tepid > ENDPOINT0; tepid--) { |
fb9987d0 S |
125 | tmp_endpoint = &target->endpoint[tepid]; |
126 | if (tmp_endpoint->service_id == service_id) { | |
127 | tmp_endpoint->service_id = 0; | |
128 | break; | |
129 | } | |
130 | } | |
131 | ||
8116daf2 | 132 | if (tepid == ENDPOINT0) |
fb9987d0 S |
133 | return; |
134 | ||
135 | endpoint->service_id = service_id; | |
136 | endpoint->max_txqdepth = tmp_endpoint->max_txqdepth; | |
137 | endpoint->ep_callbacks = tmp_endpoint->ep_callbacks; | |
138 | endpoint->ul_pipeid = tmp_endpoint->ul_pipeid; | |
139 | endpoint->dl_pipeid = tmp_endpoint->dl_pipeid; | |
140 | endpoint->max_msglen = max_msglen; | |
141 | target->conn_rsp_epid = epid; | |
142 | complete(&target->cmd_wait); | |
143 | } else { | |
144 | target->conn_rsp_epid = ENDPOINT_UNUSED; | |
145 | } | |
146 | } | |
147 | ||
148 | static int htc_config_pipe_credits(struct htc_target *target) | |
149 | { | |
150 | struct sk_buff *skb; | |
151 | struct htc_config_pipe_msg *cp_msg; | |
34edd5f6 NMG |
152 | int ret; |
153 | unsigned long time_left; | |
fb9987d0 | 154 | |
0fa35a58 | 155 | skb = alloc_skb(50 + sizeof(struct htc_frame_hdr), GFP_ATOMIC); |
fb9987d0 S |
156 | if (!skb) { |
157 | dev_err(target->dev, "failed to allocate send buffer\n"); | |
158 | return -ENOMEM; | |
159 | } | |
160 | skb_reserve(skb, sizeof(struct htc_frame_hdr)); | |
161 | ||
4df864c1 | 162 | cp_msg = skb_put(skb, sizeof(struct htc_config_pipe_msg)); |
fb9987d0 S |
163 | |
164 | cp_msg->message_id = cpu_to_be16(HTC_MSG_CONFIG_PIPE_ID); | |
165 | cp_msg->pipe_id = USB_WLAN_TX_PIPE; | |
6267dc70 | 166 | cp_msg->credits = target->credits; |
fb9987d0 S |
167 | |
168 | target->htc_flags |= HTC_OP_CONFIG_PIPE_CREDITS; | |
169 | ||
40dc9e4b | 170 | ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0); |
fb9987d0 S |
171 | if (ret) |
172 | goto err; | |
173 | ||
174 | time_left = wait_for_completion_timeout(&target->cmd_wait, HZ); | |
175 | if (!time_left) { | |
176 | dev_err(target->dev, "HTC credit config timeout\n"); | |
177 | return -ETIMEDOUT; | |
178 | } | |
179 | ||
180 | return 0; | |
181 | err: | |
0fa35a58 | 182 | kfree_skb(skb); |
fb9987d0 S |
183 | return -EINVAL; |
184 | } | |
185 | ||
186 | static int htc_setup_complete(struct htc_target *target) | |
187 | { | |
188 | struct sk_buff *skb; | |
189 | struct htc_comp_msg *comp_msg; | |
34edd5f6 NMG |
190 | int ret = 0; |
191 | unsigned long time_left; | |
fb9987d0 | 192 | |
0fa35a58 | 193 | skb = alloc_skb(50 + sizeof(struct htc_frame_hdr), GFP_ATOMIC); |
fb9987d0 S |
194 | if (!skb) { |
195 | dev_err(target->dev, "failed to allocate send buffer\n"); | |
196 | return -ENOMEM; | |
197 | } | |
198 | skb_reserve(skb, sizeof(struct htc_frame_hdr)); | |
199 | ||
4df864c1 | 200 | comp_msg = skb_put(skb, sizeof(struct htc_comp_msg)); |
fb9987d0 S |
201 | comp_msg->msg_id = cpu_to_be16(HTC_MSG_SETUP_COMPLETE_ID); |
202 | ||
203 | target->htc_flags |= HTC_OP_START_WAIT; | |
204 | ||
40dc9e4b | 205 | ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0); |
fb9987d0 S |
206 | if (ret) |
207 | goto err; | |
208 | ||
209 | time_left = wait_for_completion_timeout(&target->cmd_wait, HZ); | |
210 | if (!time_left) { | |
211 | dev_err(target->dev, "HTC start timeout\n"); | |
212 | return -ETIMEDOUT; | |
213 | } | |
214 | ||
215 | return 0; | |
216 | ||
217 | err: | |
0fa35a58 | 218 | kfree_skb(skb); |
fb9987d0 S |
219 | return -EINVAL; |
220 | } | |
221 | ||
222 | /* HTC APIs */ | |
223 | ||
224 | int htc_init(struct htc_target *target) | |
225 | { | |
226 | int ret; | |
227 | ||
228 | ret = htc_config_pipe_credits(target); | |
229 | if (ret) | |
230 | return ret; | |
231 | ||
232 | return htc_setup_complete(target); | |
233 | } | |
234 | ||
235 | int htc_connect_service(struct htc_target *target, | |
236 | struct htc_service_connreq *service_connreq, | |
237 | enum htc_endpoint_id *conn_rsp_epid) | |
238 | { | |
239 | struct sk_buff *skb; | |
240 | struct htc_endpoint *endpoint; | |
241 | struct htc_conn_svc_msg *conn_msg; | |
34edd5f6 NMG |
242 | int ret; |
243 | unsigned long time_left; | |
fb9987d0 S |
244 | |
245 | /* Find an available endpoint */ | |
246 | endpoint = get_next_avail_ep(target->endpoint); | |
247 | if (!endpoint) { | |
14acebc3 CIK |
248 | dev_err(target->dev, "Endpoint is not available for service %d\n", |
249 | service_connreq->service_id); | |
fb9987d0 S |
250 | return -EINVAL; |
251 | } | |
252 | ||
253 | endpoint->service_id = service_connreq->service_id; | |
254 | endpoint->max_txqdepth = service_connreq->max_send_qdepth; | |
255 | endpoint->ul_pipeid = service_to_ulpipe(service_connreq->service_id); | |
256 | endpoint->dl_pipeid = service_to_dlpipe(service_connreq->service_id); | |
257 | endpoint->ep_callbacks = service_connreq->ep_callbacks; | |
258 | ||
0fa35a58 ML |
259 | skb = alloc_skb(sizeof(struct htc_conn_svc_msg) + |
260 | sizeof(struct htc_frame_hdr), GFP_ATOMIC); | |
fb9987d0 S |
261 | if (!skb) { |
262 | dev_err(target->dev, "Failed to allocate buf to send" | |
263 | "service connect req\n"); | |
264 | return -ENOMEM; | |
265 | } | |
266 | ||
267 | skb_reserve(skb, sizeof(struct htc_frame_hdr)); | |
268 | ||
4df864c1 | 269 | conn_msg = skb_put(skb, sizeof(struct htc_conn_svc_msg)); |
fb9987d0 S |
270 | conn_msg->service_id = cpu_to_be16(service_connreq->service_id); |
271 | conn_msg->msg_id = cpu_to_be16(HTC_MSG_CONNECT_SERVICE_ID); | |
272 | conn_msg->con_flags = cpu_to_be16(service_connreq->con_flags); | |
273 | conn_msg->dl_pipeid = endpoint->dl_pipeid; | |
274 | conn_msg->ul_pipeid = endpoint->ul_pipeid; | |
275 | ||
b59ef5a7 PS |
276 | /* To prevent infoleak */ |
277 | conn_msg->svc_meta_len = 0; | |
278 | conn_msg->pad = 0; | |
279 | ||
40dc9e4b | 280 | ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0); |
fb9987d0 S |
281 | if (ret) |
282 | goto err; | |
283 | ||
284 | time_left = wait_for_completion_timeout(&target->cmd_wait, HZ); | |
285 | if (!time_left) { | |
286 | dev_err(target->dev, "Service connection timeout for: %d\n", | |
287 | service_connreq->service_id); | |
288 | return -ETIMEDOUT; | |
289 | } | |
290 | ||
291 | *conn_rsp_epid = target->conn_rsp_epid; | |
292 | return 0; | |
293 | err: | |
0fa35a58 | 294 | kfree_skb(skb); |
fb9987d0 S |
295 | return ret; |
296 | } | |
297 | ||
d67ee533 | 298 | int htc_send(struct htc_target *target, struct sk_buff *skb) |
fb9987d0 | 299 | { |
d67ee533 SM |
300 | struct ath9k_htc_tx_ctl *tx_ctl; |
301 | ||
302 | tx_ctl = HTC_SKB_CB(skb); | |
303 | return htc_issue_send(target, skb, skb->len, 0, tx_ctl->epid); | |
fb9987d0 S |
304 | } |
305 | ||
d67ee533 SM |
306 | int htc_send_epid(struct htc_target *target, struct sk_buff *skb, |
307 | enum htc_endpoint_id epid) | |
fb9987d0 | 308 | { |
40dc9e4b | 309 | return htc_issue_send(target, skb, skb->len, 0, epid); |
fb9987d0 | 310 | } |
fb9987d0 | 311 | |
fb9987d0 S |
312 | void htc_stop(struct htc_target *target) |
313 | { | |
e1fe7c38 | 314 | target->hif->stop(target->hif_dev); |
fb9987d0 S |
315 | } |
316 | ||
317 | void htc_start(struct htc_target *target) | |
318 | { | |
e1fe7c38 | 319 | target->hif->start(target->hif_dev); |
fb9987d0 | 320 | } |
fb9987d0 | 321 | |
84c9e164 SM |
322 | void htc_sta_drain(struct htc_target *target, u8 idx) |
323 | { | |
324 | target->hif->sta_drain(target->hif_dev, idx); | |
fb9987d0 S |
325 | } |
326 | ||
327 | void ath9k_htc_txcompletion_cb(struct htc_target *htc_handle, | |
328 | struct sk_buff *skb, bool txok) | |
329 | { | |
330 | struct htc_endpoint *endpoint; | |
0fa35a58 | 331 | struct htc_frame_hdr *htc_hdr = NULL; |
fb9987d0 S |
332 | |
333 | if (htc_handle->htc_flags & HTC_OP_CONFIG_PIPE_CREDITS) { | |
334 | complete(&htc_handle->cmd_wait); | |
335 | htc_handle->htc_flags &= ~HTC_OP_CONFIG_PIPE_CREDITS; | |
f984d94c | 336 | goto ret; |
fb9987d0 S |
337 | } |
338 | ||
339 | if (htc_handle->htc_flags & HTC_OP_START_WAIT) { | |
340 | complete(&htc_handle->cmd_wait); | |
341 | htc_handle->htc_flags &= ~HTC_OP_START_WAIT; | |
f984d94c | 342 | goto ret; |
fb9987d0 S |
343 | } |
344 | ||
345 | if (skb) { | |
346 | htc_hdr = (struct htc_frame_hdr *) skb->data; | |
2705cd75 DC |
347 | if (htc_hdr->endpoint_id >= ARRAY_SIZE(htc_handle->endpoint)) |
348 | goto ret; | |
fb9987d0 S |
349 | endpoint = &htc_handle->endpoint[htc_hdr->endpoint_id]; |
350 | skb_pull(skb, sizeof(struct htc_frame_hdr)); | |
351 | ||
352 | if (endpoint->ep_callbacks.tx) { | |
f6689072 S |
353 | endpoint->ep_callbacks.tx(endpoint->ep_callbacks.priv, |
354 | skb, htc_hdr->endpoint_id, | |
355 | txok); | |
0981c3b2 SM |
356 | } else { |
357 | kfree_skb(skb); | |
fb9987d0 S |
358 | } |
359 | } | |
f984d94c S |
360 | |
361 | return; | |
362 | ret: | |
811c69e6 | 363 | kfree_skb(skb); |
fb9987d0 S |
364 | } |
365 | ||
482b30b6 OR |
366 | static void ath9k_htc_fw_panic_report(struct htc_target *htc_handle, |
367 | struct sk_buff *skb) | |
368 | { | |
369 | uint32_t *pattern = (uint32_t *)skb->data; | |
370 | ||
371 | switch (*pattern) { | |
372 | case 0x33221199: | |
373 | { | |
374 | struct htc_panic_bad_vaddr *htc_panic; | |
375 | htc_panic = (struct htc_panic_bad_vaddr *) skb->data; | |
376 | dev_err(htc_handle->dev, "ath: firmware panic! " | |
377 | "exccause: 0x%08x; pc: 0x%08x; badvaddr: 0x%08x.\n", | |
378 | htc_panic->exccause, htc_panic->pc, | |
379 | htc_panic->badvaddr); | |
380 | break; | |
381 | } | |
382 | case 0x33221299: | |
383 | { | |
384 | struct htc_panic_bad_epid *htc_panic; | |
385 | htc_panic = (struct htc_panic_bad_epid *) skb->data; | |
386 | dev_err(htc_handle->dev, "ath: firmware panic! " | |
387 | "bad epid: 0x%08x\n", htc_panic->epid); | |
388 | break; | |
389 | } | |
390 | default: | |
14acebc3 | 391 | dev_err(htc_handle->dev, "ath: unknown panic pattern!\n"); |
482b30b6 OR |
392 | break; |
393 | } | |
394 | } | |
395 | ||
fb9987d0 S |
396 | /* |
397 | * HTC Messages are handled directly here and the obtained SKB | |
398 | * is freed. | |
399 | * | |
25985edc | 400 | * Service messages (Data, WMI) passed to the corresponding |
fb9987d0 S |
401 | * endpoint RX handlers, which have to free the SKB. |
402 | */ | |
403 | void ath9k_htc_rx_msg(struct htc_target *htc_handle, | |
404 | struct sk_buff *skb, u32 len, u8 pipe_id) | |
405 | { | |
406 | struct htc_frame_hdr *htc_hdr; | |
407 | enum htc_endpoint_id epid; | |
408 | struct htc_endpoint *endpoint; | |
7f1f5a00 | 409 | __be16 *msg_id; |
fb9987d0 S |
410 | |
411 | if (!htc_handle || !skb) | |
412 | return; | |
413 | ||
414 | htc_hdr = (struct htc_frame_hdr *) skb->data; | |
415 | epid = htc_hdr->endpoint_id; | |
416 | ||
482b30b6 OR |
417 | if (epid == 0x99) { |
418 | ath9k_htc_fw_panic_report(htc_handle, skb); | |
419 | kfree_skb(skb); | |
420 | return; | |
421 | } | |
422 | ||
3a318426 | 423 | if (epid < 0 || epid >= ENDPOINT_MAX) { |
e6c6d33c ML |
424 | if (pipe_id != USB_REG_IN_PIPE) |
425 | dev_kfree_skb_any(skb); | |
426 | else | |
427 | kfree_skb(skb); | |
fb9987d0 S |
428 | return; |
429 | } | |
430 | ||
431 | if (epid == ENDPOINT0) { | |
432 | ||
433 | /* Handle trailer */ | |
434 | if (htc_hdr->flags & HTC_FLAGS_RECV_TRAILER) { | |
7f1f5a00 | 435 | if (be32_to_cpu(*(__be32 *) skb->data) == 0x00C60000) |
fb9987d0 | 436 | /* Move past the Watchdog pattern */ |
d5a4c5e3 | 437 | htc_hdr = (struct htc_frame_hdr *)(skb->data + 4); |
fb9987d0 S |
438 | } |
439 | ||
440 | /* Get the message ID */ | |
7f1f5a00 S |
441 | msg_id = (__be16 *) ((void *) htc_hdr + |
442 | sizeof(struct htc_frame_hdr)); | |
fb9987d0 S |
443 | |
444 | /* Now process HTC messages */ | |
445 | switch (be16_to_cpu(*msg_id)) { | |
446 | case HTC_MSG_READY_ID: | |
447 | htc_process_target_rdy(htc_handle, htc_hdr); | |
448 | break; | |
449 | case HTC_MSG_CONNECT_SERVICE_RESPONSE_ID: | |
450 | htc_process_conn_rsp(htc_handle, htc_hdr); | |
451 | break; | |
452 | default: | |
453 | break; | |
454 | } | |
455 | ||
e6c6d33c | 456 | kfree_skb(skb); |
fb9987d0 S |
457 | |
458 | } else { | |
459 | if (htc_hdr->flags & HTC_FLAGS_RECV_TRAILER) | |
460 | skb_trim(skb, len - htc_hdr->control[0]); | |
461 | ||
462 | skb_pull(skb, sizeof(struct htc_frame_hdr)); | |
463 | ||
464 | endpoint = &htc_handle->endpoint[epid]; | |
465 | if (endpoint->ep_callbacks.rx) | |
466 | endpoint->ep_callbacks.rx(endpoint->ep_callbacks.priv, | |
467 | skb, epid); | |
468 | } | |
469 | } | |
470 | ||
47fce026 SM |
471 | struct htc_target *ath9k_htc_hw_alloc(void *hif_handle, |
472 | struct ath9k_htc_hif *hif, | |
473 | struct device *dev) | |
fb9987d0 | 474 | { |
47fce026 | 475 | struct htc_endpoint *endpoint; |
fb9987d0 S |
476 | struct htc_target *target; |
477 | ||
478 | target = kzalloc(sizeof(struct htc_target), GFP_KERNEL); | |
e404decb | 479 | if (!target) |
47fce026 | 480 | return NULL; |
fb9987d0 S |
481 | |
482 | init_completion(&target->target_wait); | |
483 | init_completion(&target->cmd_wait); | |
484 | ||
485 | target->hif = hif; | |
486 | target->hif_dev = hif_handle; | |
487 | target->dev = dev; | |
488 | ||
489 | /* Assign control endpoint pipe IDs */ | |
490 | endpoint = &target->endpoint[ENDPOINT0]; | |
491 | endpoint->ul_pipeid = hif->control_ul_pipe; | |
492 | endpoint->dl_pipeid = hif->control_dl_pipe; | |
493 | ||
d8c49ffb SM |
494 | atomic_set(&target->tgt_ready, 0); |
495 | ||
47fce026 SM |
496 | return target; |
497 | } | |
498 | ||
499 | void ath9k_htc_hw_free(struct htc_target *htc) | |
500 | { | |
501 | kfree(htc); | |
502 | } | |
503 | ||
504 | int ath9k_htc_hw_init(struct htc_target *target, | |
fa6e15e0 RM |
505 | struct device *dev, u16 devid, |
506 | char *product, u32 drv_info) | |
47fce026 | 507 | { |
fa6e15e0 | 508 | if (ath9k_htc_probe_device(target, dev, devid, product, drv_info)) { |
516304b0 | 509 | pr_err("Failed to initialize the device\n"); |
fb9987d0 S |
510 | return -ENODEV; |
511 | } | |
512 | ||
513 | return 0; | |
514 | } | |
515 | ||
516 | void ath9k_htc_hw_deinit(struct htc_target *target, bool hot_unplug) | |
517 | { | |
518 | if (target) | |
519 | ath9k_htc_disconnect_device(target, hot_unplug); | |
520 | } |