aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/iwmc3200wifi/hal.c
diff options
context:
space:
mode:
authorZhu Yi <yi.zhu@intel.com>2009-05-21 09:20:45 -0400
committerJohn W. Linville <linville@tuxdriver.com>2009-05-22 14:06:02 -0400
commitbb9f8692f5043efef0dcef048cdd1db68299c2cb (patch)
treede0eade3ea6e40341727789a3a91e5c506b68759 /drivers/net/wireless/iwmc3200wifi/hal.c
parente31a16d6f64ef0e324c6f54d5112703c3f13a9c4 (diff)
iwmc3200wifi: Add new Intel Wireless Multicomm 802.11 driver
This driver supports Intel's full MAC wireless multicomm 802.11 hardware. Although the hardware is a 802.11agn device, we currently only support 802.11ag, in managed and ad-hoc mode (no AP mode for now). Signed-off-by: Zhu Yi <yi.zhu@intel.com> Signed-off-by: Samuel Ortiz <samuel.ortiz@intel.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'drivers/net/wireless/iwmc3200wifi/hal.c')
-rw-r--r--drivers/net/wireless/iwmc3200wifi/hal.c464
1 files changed, 464 insertions, 0 deletions
diff --git a/drivers/net/wireless/iwmc3200wifi/hal.c b/drivers/net/wireless/iwmc3200wifi/hal.c
new file mode 100644
index 000000000000..ee127fe4f43f
--- /dev/null
+++ b/drivers/net/wireless/iwmc3200wifi/hal.c
@@ -0,0 +1,464 @@
1/*
2 * Intel Wireless Multicomm 3200 WiFi driver
3 *
4 * Copyright (C) 2009 Intel Corporation. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
16 * * Neither the name of Intel Corporation nor the names of its
17 * contributors may be used to endorse or promote products derived
18 * from this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 *
32 *
33 * Intel Corporation <ilw@linux.intel.com>
34 * Samuel Ortiz <samuel.ortiz@intel.com>
35 * Zhu Yi <yi.zhu@intel.com>
36 *
37 */
38
39/*
40 * Hardware Abstraction Layer for iwm.
41 *
42 * This file mostly defines an abstraction API for
43 * sending various commands to the target.
44 *
45 * We have 2 types of commands: wifi and non-wifi ones.
46 *
47 * - wifi commands:
48 * They are used for sending LMAC and UMAC commands,
49 * and thus are the most commonly used ones.
50 * There are 2 different wifi command types, the regular
51 * one and the LMAC one. The former is used to send
52 * UMAC commands (see UMAC_CMD_OPCODE_* from umac.h)
53 * while the latter is used for sending commands to the
54 * LMAC. If you look at LMAC commands you'll se that they
55 * are actually regular iwlwifi target commands encapsulated
56 * into a special UMAC command called UMAC passthrough.
57 * This is due to the fact the the host talks exclusively
58 * to the UMAC and so there needs to be a special UMAC
59 * command for talking to the LMAC.
60 * This is how a wifi command is layed out:
61 * ------------------------
62 * | iwm_udma_out_wifi_hdr |
63 * ------------------------
64 * | SW meta_data (32 bits) |
65 * ------------------------
66 * | iwm_dev_cmd_hdr |
67 * ------------------------
68 * | payload |
69 * | .... |
70 *
71 * - non-wifi, or general commands:
72 * Those commands are handled by the device's bootrom,
73 * and are typically sent when the UMAC and the LMAC
74 * are not yet available.
75 * * This is how a non-wifi command is layed out:
76 * ---------------------------
77 * | iwm_udma_out_nonwifi_hdr |
78 * ---------------------------
79 * | payload |
80 * | .... |
81
82 *
83 * All the commands start with a UDMA header, which is
84 * basically a 32 bits field. The 4 LSB there define
85 * an opcode that allows the target to differentiate
86 * between wifi (opcode is 0xf) and non-wifi commands
87 * (opcode is [0..0xe]).
88 *
89 * When a command (wifi or non-wifi) is supposed to receive
90 * an answer, we queue the command buffer. When we do receive
91 * a command response from the UMAC, we go through the list
92 * of pending command, and pass both the command and the answer
93 * to the rx handler. Each command is sent with a unique
94 * sequence id, and the answer is sent with the same one. This
95 * is how we're supposed to match an answer with its command.
96 * See rx.c:iwm_rx_handle_[non]wifi() and iwm_get_pending_[non]wifi()
97 * for the implementation details.
98 */
99#include <linux/kernel.h>
100#include <linux/netdevice.h>
101
102#include "iwm.h"
103#include "bus.h"
104#include "hal.h"
105#include "umac.h"
106#include "debug.h"
107
108static void iwm_nonwifi_cmd_init(struct iwm_priv *iwm,
109 struct iwm_nonwifi_cmd *cmd,
110 struct iwm_udma_nonwifi_cmd *udma_cmd)
111{
112 INIT_LIST_HEAD(&cmd->pending);
113
114 spin_lock(&iwm->cmd_lock);
115
116 cmd->resp_received = 0;
117
118 cmd->seq_num = iwm->nonwifi_seq_num;
119 udma_cmd->seq_num = cpu_to_le16(cmd->seq_num);
120
121 cmd->seq_num = iwm->nonwifi_seq_num++;
122 iwm->nonwifi_seq_num %= UMAC_NONWIFI_SEQ_NUM_MAX;
123
124 if (udma_cmd->resp)
125 list_add_tail(&cmd->pending, &iwm->nonwifi_pending_cmd);
126
127 spin_unlock(&iwm->cmd_lock);
128
129 cmd->buf.start = cmd->buf.payload;
130 cmd->buf.len = 0;
131
132 memcpy(&cmd->udma_cmd, udma_cmd, sizeof(*udma_cmd));
133}
134
135u16 iwm_alloc_wifi_cmd_seq(struct iwm_priv *iwm)
136{
137 u16 seq_num = iwm->wifi_seq_num;
138
139 iwm->wifi_seq_num++;
140 iwm->wifi_seq_num %= UMAC_WIFI_SEQ_NUM_MAX;
141
142 return seq_num;
143}
144
145static void iwm_wifi_cmd_init(struct iwm_priv *iwm,
146 struct iwm_wifi_cmd *cmd,
147 struct iwm_udma_wifi_cmd *udma_cmd,
148 struct iwm_umac_cmd *umac_cmd,
149 struct iwm_lmac_cmd *lmac_cmd,
150 u16 payload_size)
151{
152 INIT_LIST_HEAD(&cmd->pending);
153
154 spin_lock(&iwm->cmd_lock);
155
156 cmd->seq_num = iwm_alloc_wifi_cmd_seq(iwm);
157 umac_cmd->seq_num = cpu_to_le16(cmd->seq_num);
158
159 if (umac_cmd->resp)
160 list_add_tail(&cmd->pending, &iwm->wifi_pending_cmd);
161
162 spin_unlock(&iwm->cmd_lock);
163
164 cmd->buf.start = cmd->buf.payload;
165 cmd->buf.len = 0;
166
167 if (lmac_cmd) {
168 cmd->buf.start -= sizeof(struct iwm_lmac_hdr);
169
170 lmac_cmd->seq_num = cpu_to_le16(cmd->seq_num);
171 lmac_cmd->count = cpu_to_le16(payload_size);
172
173 memcpy(&cmd->lmac_cmd, lmac_cmd, sizeof(*lmac_cmd));
174
175 umac_cmd->count = cpu_to_le16(sizeof(struct iwm_lmac_hdr));
176 } else
177 umac_cmd->count = 0;
178
179 umac_cmd->count = cpu_to_le16(payload_size +
180 le16_to_cpu(umac_cmd->count));
181 udma_cmd->count = cpu_to_le16(sizeof(struct iwm_umac_fw_cmd_hdr) +
182 le16_to_cpu(umac_cmd->count));
183
184 memcpy(&cmd->udma_cmd, udma_cmd, sizeof(*udma_cmd));
185 memcpy(&cmd->umac_cmd, umac_cmd, sizeof(*umac_cmd));
186}
187
188void iwm_cmd_flush(struct iwm_priv *iwm)
189{
190 struct iwm_wifi_cmd *wcmd, *wnext;
191 struct iwm_nonwifi_cmd *nwcmd, *nwnext;
192
193 list_for_each_entry_safe(wcmd, wnext, &iwm->wifi_pending_cmd, pending) {
194 list_del(&wcmd->pending);
195 kfree(wcmd);
196 }
197
198 list_for_each_entry_safe(nwcmd, nwnext, &iwm->nonwifi_pending_cmd,
199 pending) {
200 list_del(&nwcmd->pending);
201 kfree(nwcmd);
202 }
203}
204
205struct iwm_wifi_cmd *iwm_get_pending_wifi_cmd(struct iwm_priv *iwm, u16 seq_num)
206{
207 struct iwm_wifi_cmd *cmd, *next;
208
209 list_for_each_entry_safe(cmd, next, &iwm->wifi_pending_cmd, pending)
210 if (cmd->seq_num == seq_num) {
211 list_del(&cmd->pending);
212 return cmd;
213 }
214
215 return NULL;
216}
217
218struct iwm_nonwifi_cmd *
219iwm_get_pending_nonwifi_cmd(struct iwm_priv *iwm, u8 seq_num, u8 cmd_opcode)
220{
221 struct iwm_nonwifi_cmd *cmd, *next;
222
223 list_for_each_entry_safe(cmd, next, &iwm->nonwifi_pending_cmd, pending)
224 if ((cmd->seq_num == seq_num) &&
225 (cmd->udma_cmd.opcode == cmd_opcode) &&
226 (cmd->resp_received)) {
227 list_del(&cmd->pending);
228 return cmd;
229 }
230
231 return NULL;
232}
233
234static void iwm_build_udma_nonwifi_hdr(struct iwm_priv *iwm,
235 struct iwm_udma_out_nonwifi_hdr *hdr,
236 struct iwm_udma_nonwifi_cmd *cmd)
237{
238 memset(hdr, 0, sizeof(*hdr));
239
240 SET_VAL32(hdr->cmd, UMAC_HDI_OUT_CMD_OPCODE, cmd->opcode);
241 SET_VAL32(hdr->cmd, UDMA_HDI_OUT_NW_CMD_RESP, cmd->resp);
242 SET_VAL32(hdr->cmd, UMAC_HDI_OUT_CMD_EOT, 1);
243 SET_VAL32(hdr->cmd, UDMA_HDI_OUT_NW_CMD_HANDLE_BY_HW,
244 cmd->handle_by_hw);
245 SET_VAL32(hdr->cmd, UMAC_HDI_OUT_CMD_SIGNATURE, UMAC_HDI_OUT_SIGNATURE);
246 SET_VAL32(hdr->cmd, UDMA_HDI_OUT_CMD_NON_WIFI_HW_SEQ_NUM,
247 le16_to_cpu(cmd->seq_num));
248
249 hdr->addr = cmd->addr;
250 hdr->op1_sz = cmd->op1_sz;
251 hdr->op2 = cmd->op2;
252}
253
254static int iwm_send_udma_nonwifi_cmd(struct iwm_priv *iwm,
255 struct iwm_nonwifi_cmd *cmd)
256{
257 struct iwm_udma_out_nonwifi_hdr *udma_hdr;
258 struct iwm_nonwifi_cmd_buff *buf;
259 struct iwm_udma_nonwifi_cmd *udma_cmd = &cmd->udma_cmd;
260
261 buf = &cmd->buf;
262
263 buf->start -= sizeof(struct iwm_umac_nonwifi_out_hdr);
264 buf->len += sizeof(struct iwm_umac_nonwifi_out_hdr);
265
266 udma_hdr = (struct iwm_udma_out_nonwifi_hdr *)(buf->start);
267
268 iwm_build_udma_nonwifi_hdr(iwm, udma_hdr, udma_cmd);
269
270 IWM_DBG_CMD(iwm, DBG,
271 "Send UDMA nonwifi cmd: opcode = 0x%x, resp = 0x%x, "
272 "hw = 0x%x, seqnum = %d, addr = 0x%x, op1_sz = 0x%x, "
273 "op2 = 0x%x\n", udma_cmd->opcode, udma_cmd->resp,
274 udma_cmd->handle_by_hw, cmd->seq_num, udma_cmd->addr,
275 udma_cmd->op1_sz, udma_cmd->op2);
276
277 return iwm_bus_send_chunk(iwm, buf->start, buf->len);
278}
279
280void iwm_udma_wifi_hdr_set_eop(struct iwm_priv *iwm, u8 *buf, u8 eop)
281{
282 struct iwm_udma_out_wifi_hdr *hdr = (struct iwm_udma_out_wifi_hdr *)buf;
283
284 SET_VAL32(hdr->cmd, UMAC_HDI_OUT_CMD_EOT, eop);
285}
286
287void iwm_build_udma_wifi_hdr(struct iwm_priv *iwm,
288 struct iwm_udma_out_wifi_hdr *hdr,
289 struct iwm_udma_wifi_cmd *cmd)
290{
291 memset(hdr, 0, sizeof(*hdr));
292
293 SET_VAL32(hdr->cmd, UMAC_HDI_OUT_CMD_OPCODE, UMAC_HDI_OUT_OPCODE_WIFI);
294 SET_VAL32(hdr->cmd, UMAC_HDI_OUT_CMD_EOT, cmd->eop);
295 SET_VAL32(hdr->cmd, UMAC_HDI_OUT_CMD_SIGNATURE, UMAC_HDI_OUT_SIGNATURE);
296
297 SET_VAL32(hdr->meta_data, UMAC_HDI_OUT_BYTE_COUNT,
298 le16_to_cpu(cmd->count));
299 SET_VAL32(hdr->meta_data, UMAC_HDI_OUT_CREDIT_GRP, cmd->credit_group);
300 SET_VAL32(hdr->meta_data, UMAC_HDI_OUT_RATID, cmd->ra_tid);
301 SET_VAL32(hdr->meta_data, UMAC_HDI_OUT_LMAC_OFFSET, cmd->lmac_offset);
302}
303
304void iwm_build_umac_hdr(struct iwm_priv *iwm,
305 struct iwm_umac_fw_cmd_hdr *hdr,
306 struct iwm_umac_cmd *cmd)
307{
308 memset(hdr, 0, sizeof(*hdr));
309
310 SET_VAL32(hdr->meta_data, UMAC_FW_CMD_BYTE_COUNT,
311 le16_to_cpu(cmd->count));
312 SET_VAL32(hdr->meta_data, UMAC_FW_CMD_TX_STA_COLOR, cmd->color);
313 SET_VAL8(hdr->cmd.flags, UMAC_DEV_CMD_FLAGS_RESP_REQ, cmd->resp);
314
315 hdr->cmd.cmd = cmd->id;
316 hdr->cmd.seq_num = cmd->seq_num;
317}
318
319static int iwm_send_udma_wifi_cmd(struct iwm_priv *iwm,
320 struct iwm_wifi_cmd *cmd)
321{
322 struct iwm_umac_wifi_out_hdr *umac_hdr;
323 struct iwm_wifi_cmd_buff *buf;
324 struct iwm_udma_wifi_cmd *udma_cmd = &cmd->udma_cmd;
325 struct iwm_umac_cmd *umac_cmd = &cmd->umac_cmd;
326 int ret;
327
328 buf = &cmd->buf;
329
330 buf->start -= sizeof(struct iwm_umac_wifi_out_hdr);
331 buf->len += sizeof(struct iwm_umac_wifi_out_hdr);
332
333 umac_hdr = (struct iwm_umac_wifi_out_hdr *)(buf->start);
334
335 iwm_build_udma_wifi_hdr(iwm, &umac_hdr->hw_hdr, udma_cmd);
336 iwm_build_umac_hdr(iwm, &umac_hdr->sw_hdr, umac_cmd);
337
338 IWM_DBG_CMD(iwm, DBG,
339 "Send UDMA wifi cmd: opcode = 0x%x, UMAC opcode = 0x%x, "
340 "eop = 0x%x, count = 0x%x, credit_group = 0x%x, "
341 "ra_tid = 0x%x, lmac_offset = 0x%x, seqnum = %d\n",
342 UMAC_HDI_OUT_OPCODE_WIFI, umac_cmd->id,
343 udma_cmd->eop, udma_cmd->count, udma_cmd->credit_group,
344 udma_cmd->ra_tid, udma_cmd->lmac_offset, cmd->seq_num);
345
346 if (umac_cmd->id == UMAC_CMD_OPCODE_WIFI_PASS_THROUGH)
347 IWM_DBG_CMD(iwm, DBG, "\tLMAC opcode: 0x%x\n",
348 cmd->lmac_cmd.id);
349
350 ret = iwm_tx_credit_alloc(iwm, udma_cmd->credit_group, buf->len);
351
352 /* We keep sending UMAC reset regardless of the command credits.
353 * The UMAC is supposed to be reset anyway and the Tx credits are
354 * reinitialized afterwards. If we are lucky, the reset could
355 * still be done even though we have run out of credits for the
356 * command pool at this moment.*/
357 if (ret && (umac_cmd->id != UMAC_CMD_OPCODE_RESET)) {
358 IWM_DBG_TX(iwm, DBG, "Failed to alloc tx credit for cmd %d\n",
359 umac_cmd->id);
360 return ret;
361 }
362
363 return iwm_bus_send_chunk(iwm, buf->start, buf->len);
364}
365
366/* target_cmd a.k.a udma_nonwifi_cmd can be sent when UMAC is not available */
367int iwm_hal_send_target_cmd(struct iwm_priv *iwm,
368 struct iwm_udma_nonwifi_cmd *udma_cmd,
369 const void *payload)
370{
371 struct iwm_nonwifi_cmd *cmd;
372 int ret;
373
374 cmd = kzalloc(sizeof(struct iwm_nonwifi_cmd), GFP_KERNEL);
375 if (!cmd) {
376 IWM_ERR(iwm, "Couldn't alloc memory for hal cmd\n");
377 return -ENOMEM;
378 }
379
380 iwm_nonwifi_cmd_init(iwm, cmd, udma_cmd);
381
382 if (cmd->udma_cmd.opcode == UMAC_HDI_OUT_OPCODE_WRITE ||
383 cmd->udma_cmd.opcode == UMAC_HDI_OUT_OPCODE_WRITE_PERSISTENT) {
384 cmd->buf.len = le32_to_cpu(cmd->udma_cmd.op1_sz);
385 memcpy(&cmd->buf.payload, payload, cmd->buf.len);
386 }
387
388 ret = iwm_send_udma_nonwifi_cmd(iwm, cmd);
389
390 if (!udma_cmd->resp)
391 kfree(cmd);
392
393 if (ret < 0)
394 return ret;
395
396 return cmd->seq_num;
397}
398
399static void iwm_build_lmac_hdr(struct iwm_priv *iwm, struct iwm_lmac_hdr *hdr,
400 struct iwm_lmac_cmd *cmd)
401{
402 memset(hdr, 0, sizeof(*hdr));
403
404 hdr->id = cmd->id;
405 hdr->flags = 0; /* Is this ever used? */
406 hdr->seq_num = cmd->seq_num;
407}
408
409/*
410 * iwm_hal_send_host_cmd(): sends commands to the UMAC or the LMAC.
411 * Sending command to the LMAC is equivalent to sending a
412 * regular UMAC command with the LMAC passtrough or the LMAC
413 * wrapper UMAC command IDs.
414 */
415int iwm_hal_send_host_cmd(struct iwm_priv *iwm,
416 struct iwm_udma_wifi_cmd *udma_cmd,
417 struct iwm_umac_cmd *umac_cmd,
418 struct iwm_lmac_cmd *lmac_cmd,
419 const void *payload, u16 payload_size)
420{
421 struct iwm_wifi_cmd *cmd;
422 struct iwm_lmac_hdr *hdr;
423 int lmac_hdr_len = 0;
424 int ret;
425
426 cmd = kzalloc(sizeof(struct iwm_wifi_cmd), GFP_KERNEL);
427 if (!cmd) {
428 IWM_ERR(iwm, "Couldn't alloc memory for wifi hal cmd\n");
429 return -ENOMEM;
430 }
431
432 iwm_wifi_cmd_init(iwm, cmd, udma_cmd, umac_cmd, lmac_cmd, payload_size);
433
434 if (lmac_cmd) {
435 hdr = (struct iwm_lmac_hdr *)(cmd->buf.start);
436
437 iwm_build_lmac_hdr(iwm, hdr, &cmd->lmac_cmd);
438 lmac_hdr_len = sizeof(struct iwm_lmac_hdr);
439 }
440
441 memcpy(cmd->buf.payload, payload, payload_size);
442 cmd->buf.len = le16_to_cpu(umac_cmd->count);
443
444 ret = iwm_send_udma_wifi_cmd(iwm, cmd);
445
446 /* We free the cmd if we're not expecting any response */
447 if (!umac_cmd->resp)
448 kfree(cmd);
449 return ret;
450}
451
452/*
453 * iwm_hal_send_umac_cmd(): This is a special case for
454 * iwm_hal_send_host_cmd() to send direct UMAC cmd (without
455 * LMAC involved).
456 */
457int iwm_hal_send_umac_cmd(struct iwm_priv *iwm,
458 struct iwm_udma_wifi_cmd *udma_cmd,
459 struct iwm_umac_cmd *umac_cmd,
460 const void *payload, u16 payload_size)
461{
462 return iwm_hal_send_host_cmd(iwm, udma_cmd, umac_cmd, NULL,
463 payload, payload_size);
464}