diff options
Diffstat (limited to 'drivers/misc/iwmc3200top/main.c')
-rw-r--r-- | drivers/misc/iwmc3200top/main.c | 678 |
1 files changed, 678 insertions, 0 deletions
diff --git a/drivers/misc/iwmc3200top/main.c b/drivers/misc/iwmc3200top/main.c new file mode 100644 index 000000000000..fafcaa481d74 --- /dev/null +++ b/drivers/misc/iwmc3200top/main.c | |||
@@ -0,0 +1,678 @@ | |||
1 | /* | ||
2 | * iwmc3200top - Intel Wireless MultiCom 3200 Top Driver | ||
3 | * drivers/misc/iwmc3200top/main.c | ||
4 | * | ||
5 | * Copyright (C) 2009 Intel Corporation. All rights reserved. | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License version | ||
9 | * 2 as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
19 | * 02110-1301, USA. | ||
20 | * | ||
21 | * | ||
22 | * Author Name: Maxim Grabarnik <maxim.grabarnink@intel.com> | ||
23 | * - | ||
24 | * | ||
25 | */ | ||
26 | |||
27 | #include <linux/module.h> | ||
28 | #include <linux/init.h> | ||
29 | #include <linux/kernel.h> | ||
30 | #include <linux/debugfs.h> | ||
31 | #include <linux/mmc/sdio_ids.h> | ||
32 | #include <linux/mmc/sdio_func.h> | ||
33 | #include <linux/mmc/sdio.h> | ||
34 | |||
35 | #include "iwmc3200top.h" | ||
36 | #include "log.h" | ||
37 | #include "fw-msg.h" | ||
38 | #include "debugfs.h" | ||
39 | |||
40 | |||
41 | #define DRIVER_DESCRIPTION "Intel(R) IWMC 3200 Top Driver" | ||
42 | #define DRIVER_COPYRIGHT "Copyright (c) 2008 Intel Corporation." | ||
43 | |||
44 | #define DRIVER_VERSION "0.1.62" | ||
45 | |||
46 | MODULE_DESCRIPTION(DRIVER_DESCRIPTION); | ||
47 | MODULE_VERSION(DRIVER_VERSION); | ||
48 | MODULE_LICENSE("GPL"); | ||
49 | MODULE_AUTHOR(DRIVER_COPYRIGHT); | ||
50 | MODULE_FIRMWARE(FW_NAME(FW_API_VER)); | ||
51 | |||
52 | /* | ||
53 | * This workers main task is to wait for OP_OPR_ALIVE | ||
54 | * from TOP FW until ALIVE_MSG_TIMOUT timeout is elapsed. | ||
55 | * When OP_OPR_ALIVE received it will issue | ||
56 | * a call to "bus_rescan_devices". | ||
57 | */ | ||
58 | static void iwmct_rescan_worker(struct work_struct *ws) | ||
59 | { | ||
60 | struct iwmct_priv *priv; | ||
61 | int ret; | ||
62 | |||
63 | priv = container_of(ws, struct iwmct_priv, bus_rescan_worker); | ||
64 | |||
65 | LOG_INFO(priv, FW_MSG, "Calling bus_rescan\n"); | ||
66 | |||
67 | ret = bus_rescan_devices(priv->func->dev.bus); | ||
68 | if (ret < 0) | ||
69 | LOG_INFO(priv, FW_DOWNLOAD, "bus_rescan_devices FAILED!!!\n"); | ||
70 | } | ||
71 | |||
72 | static void op_top_message(struct iwmct_priv *priv, struct top_msg *msg) | ||
73 | { | ||
74 | switch (msg->hdr.opcode) { | ||
75 | case OP_OPR_ALIVE: | ||
76 | LOG_INFO(priv, FW_MSG, "Got ALIVE from device, wake rescan\n"); | ||
77 | queue_work(priv->bus_rescan_wq, &priv->bus_rescan_worker); | ||
78 | break; | ||
79 | default: | ||
80 | LOG_INFO(priv, FW_MSG, "Received msg opcode 0x%X\n", | ||
81 | msg->hdr.opcode); | ||
82 | break; | ||
83 | } | ||
84 | } | ||
85 | |||
86 | |||
87 | static void handle_top_message(struct iwmct_priv *priv, u8 *buf, int len) | ||
88 | { | ||
89 | struct top_msg *msg; | ||
90 | |||
91 | msg = (struct top_msg *)buf; | ||
92 | |||
93 | if (msg->hdr.type != COMM_TYPE_D2H) { | ||
94 | LOG_ERROR(priv, FW_MSG, | ||
95 | "Message from TOP with invalid message type 0x%X\n", | ||
96 | msg->hdr.type); | ||
97 | return; | ||
98 | } | ||
99 | |||
100 | if (len < sizeof(msg->hdr)) { | ||
101 | LOG_ERROR(priv, FW_MSG, | ||
102 | "Message from TOP is too short for message header " | ||
103 | "received %d bytes, expected at least %zd bytes\n", | ||
104 | len, sizeof(msg->hdr)); | ||
105 | return; | ||
106 | } | ||
107 | |||
108 | if (len < le16_to_cpu(msg->hdr.length) + sizeof(msg->hdr)) { | ||
109 | LOG_ERROR(priv, FW_MSG, | ||
110 | "Message length (%d bytes) is shorter than " | ||
111 | "in header (%d bytes)\n", | ||
112 | len, le16_to_cpu(msg->hdr.length)); | ||
113 | return; | ||
114 | } | ||
115 | |||
116 | switch (msg->hdr.category) { | ||
117 | case COMM_CATEGORY_OPERATIONAL: | ||
118 | op_top_message(priv, (struct top_msg *)buf); | ||
119 | break; | ||
120 | |||
121 | case COMM_CATEGORY_DEBUG: | ||
122 | case COMM_CATEGORY_TESTABILITY: | ||
123 | case COMM_CATEGORY_DIAGNOSTICS: | ||
124 | iwmct_log_top_message(priv, buf, len); | ||
125 | break; | ||
126 | |||
127 | default: | ||
128 | LOG_ERROR(priv, FW_MSG, | ||
129 | "Message from TOP with unknown category 0x%X\n", | ||
130 | msg->hdr.category); | ||
131 | break; | ||
132 | } | ||
133 | } | ||
134 | |||
135 | int iwmct_send_hcmd(struct iwmct_priv *priv, u8 *cmd, u16 len) | ||
136 | { | ||
137 | int ret; | ||
138 | u8 *buf; | ||
139 | |||
140 | LOG_INFOEX(priv, FW_MSG, "Sending hcmd:\n"); | ||
141 | |||
142 | /* add padding to 256 for IWMC */ | ||
143 | ((struct top_msg *)cmd)->hdr.flags |= CMD_FLAG_PADDING_256; | ||
144 | |||
145 | LOG_HEXDUMP(FW_MSG, cmd, len); | ||
146 | |||
147 | if (len > FW_HCMD_BLOCK_SIZE) { | ||
148 | LOG_ERROR(priv, FW_MSG, "size %d exceeded hcmd max size %d\n", | ||
149 | len, FW_HCMD_BLOCK_SIZE); | ||
150 | return -1; | ||
151 | } | ||
152 | |||
153 | buf = kzalloc(FW_HCMD_BLOCK_SIZE, GFP_KERNEL); | ||
154 | if (!buf) { | ||
155 | LOG_ERROR(priv, FW_MSG, "kzalloc error, buf size %d\n", | ||
156 | FW_HCMD_BLOCK_SIZE); | ||
157 | return -1; | ||
158 | } | ||
159 | |||
160 | memcpy(buf, cmd, len); | ||
161 | |||
162 | sdio_claim_host(priv->func); | ||
163 | ret = sdio_memcpy_toio(priv->func, IWMC_SDIO_DATA_ADDR, buf, | ||
164 | FW_HCMD_BLOCK_SIZE); | ||
165 | sdio_release_host(priv->func); | ||
166 | |||
167 | kfree(buf); | ||
168 | return ret; | ||
169 | } | ||
170 | |||
171 | int iwmct_tx(struct iwmct_priv *priv, unsigned int addr, | ||
172 | void *src, int count) | ||
173 | { | ||
174 | int ret; | ||
175 | |||
176 | sdio_claim_host(priv->func); | ||
177 | ret = sdio_memcpy_toio(priv->func, addr, src, count); | ||
178 | sdio_release_host(priv->func); | ||
179 | |||
180 | return ret; | ||
181 | } | ||
182 | |||
183 | static void iwmct_irq_read_worker(struct work_struct *ws) | ||
184 | { | ||
185 | struct iwmct_priv *priv; | ||
186 | struct iwmct_work_struct *read_req; | ||
187 | __le32 *buf = NULL; | ||
188 | int ret; | ||
189 | int iosize; | ||
190 | u32 barker; | ||
191 | bool is_barker; | ||
192 | |||
193 | priv = container_of(ws, struct iwmct_priv, isr_worker); | ||
194 | |||
195 | LOG_INFO(priv, IRQ, "enter iwmct_irq_read_worker %p\n", ws); | ||
196 | |||
197 | /* --------------------- Handshake with device -------------------- */ | ||
198 | sdio_claim_host(priv->func); | ||
199 | |||
200 | /* all list manipulations have to be protected by | ||
201 | * sdio_claim_host/sdio_release_host */ | ||
202 | if (list_empty(&priv->read_req_list)) { | ||
203 | LOG_ERROR(priv, IRQ, "read_req_list empty in read worker\n"); | ||
204 | goto exit_release; | ||
205 | } | ||
206 | |||
207 | read_req = list_entry(priv->read_req_list.next, | ||
208 | struct iwmct_work_struct, list); | ||
209 | |||
210 | list_del(&read_req->list); | ||
211 | iosize = read_req->iosize; | ||
212 | kfree(read_req); | ||
213 | |||
214 | buf = kzalloc(iosize, GFP_KERNEL); | ||
215 | if (!buf) { | ||
216 | LOG_ERROR(priv, IRQ, "kzalloc error, buf size %d\n", iosize); | ||
217 | goto exit_release; | ||
218 | } | ||
219 | |||
220 | LOG_INFO(priv, IRQ, "iosize=%d, buf=%p, func=%d\n", | ||
221 | iosize, buf, priv->func->num); | ||
222 | |||
223 | /* read from device */ | ||
224 | ret = sdio_memcpy_fromio(priv->func, buf, IWMC_SDIO_DATA_ADDR, iosize); | ||
225 | if (ret) { | ||
226 | LOG_ERROR(priv, IRQ, "error %d reading buffer\n", ret); | ||
227 | goto exit_release; | ||
228 | } | ||
229 | |||
230 | LOG_HEXDUMP(IRQ, (u8 *)buf, iosize); | ||
231 | |||
232 | barker = le32_to_cpu(buf[0]); | ||
233 | |||
234 | /* Verify whether it's a barker and if not - treat as regular Rx */ | ||
235 | if (barker == IWMC_BARKER_ACK || | ||
236 | (barker & BARKER_DNLOAD_BARKER_MSK) == IWMC_BARKER_REBOOT) { | ||
237 | |||
238 | /* Valid Barker is equal on first 4 dwords */ | ||
239 | is_barker = (buf[1] == buf[0]) && | ||
240 | (buf[2] == buf[0]) && | ||
241 | (buf[3] == buf[0]); | ||
242 | |||
243 | if (!is_barker) { | ||
244 | LOG_WARNING(priv, IRQ, | ||
245 | "Potentially inconsistent barker " | ||
246 | "%08X_%08X_%08X_%08X\n", | ||
247 | le32_to_cpu(buf[0]), le32_to_cpu(buf[1]), | ||
248 | le32_to_cpu(buf[2]), le32_to_cpu(buf[3])); | ||
249 | } | ||
250 | } else { | ||
251 | is_barker = false; | ||
252 | } | ||
253 | |||
254 | /* Handle Top CommHub message */ | ||
255 | if (!is_barker) { | ||
256 | sdio_release_host(priv->func); | ||
257 | handle_top_message(priv, (u8 *)buf, iosize); | ||
258 | goto exit; | ||
259 | } else if (barker == IWMC_BARKER_ACK) { /* Handle barkers */ | ||
260 | if (atomic_read(&priv->dev_sync) == 0) { | ||
261 | LOG_ERROR(priv, IRQ, | ||
262 | "ACK barker arrived out-of-sync\n"); | ||
263 | goto exit_release; | ||
264 | } | ||
265 | |||
266 | /* Continuing to FW download (after Sync is completed)*/ | ||
267 | atomic_set(&priv->dev_sync, 0); | ||
268 | LOG_INFO(priv, IRQ, "ACK barker arrived " | ||
269 | "- starting FW download\n"); | ||
270 | } else { /* REBOOT barker */ | ||
271 | LOG_INFO(priv, IRQ, "Recieved reboot barker: %x\n", barker); | ||
272 | priv->barker = barker; | ||
273 | |||
274 | if (barker & BARKER_DNLOAD_SYNC_MSK) { | ||
275 | /* Send the same barker back */ | ||
276 | ret = sdio_memcpy_toio(priv->func, IWMC_SDIO_DATA_ADDR, | ||
277 | buf, iosize); | ||
278 | if (ret) { | ||
279 | LOG_ERROR(priv, IRQ, | ||
280 | "error %d echoing barker\n", ret); | ||
281 | goto exit_release; | ||
282 | } | ||
283 | LOG_INFO(priv, IRQ, "Echoing barker to device\n"); | ||
284 | atomic_set(&priv->dev_sync, 1); | ||
285 | goto exit_release; | ||
286 | } | ||
287 | |||
288 | /* Continuing to FW download (without Sync) */ | ||
289 | LOG_INFO(priv, IRQ, "No sync requested " | ||
290 | "- starting FW download\n"); | ||
291 | } | ||
292 | |||
293 | sdio_release_host(priv->func); | ||
294 | |||
295 | |||
296 | LOG_INFO(priv, IRQ, "barker download request 0x%x is:\n", priv->barker); | ||
297 | LOG_INFO(priv, IRQ, "******* Top FW %s requested ********\n", | ||
298 | (priv->barker & BARKER_DNLOAD_TOP_MSK) ? "was" : "not"); | ||
299 | LOG_INFO(priv, IRQ, "******* GPS FW %s requested ********\n", | ||
300 | (priv->barker & BARKER_DNLOAD_GPS_MSK) ? "was" : "not"); | ||
301 | LOG_INFO(priv, IRQ, "******* BT FW %s requested ********\n", | ||
302 | (priv->barker & BARKER_DNLOAD_BT_MSK) ? "was" : "not"); | ||
303 | |||
304 | if (priv->dbg.fw_download) | ||
305 | iwmct_fw_load(priv); | ||
306 | else | ||
307 | LOG_ERROR(priv, IRQ, "FW download not allowed\n"); | ||
308 | |||
309 | goto exit; | ||
310 | |||
311 | exit_release: | ||
312 | sdio_release_host(priv->func); | ||
313 | exit: | ||
314 | kfree(buf); | ||
315 | LOG_INFO(priv, IRQ, "exit iwmct_irq_read_worker\n"); | ||
316 | } | ||
317 | |||
318 | static void iwmct_irq(struct sdio_func *func) | ||
319 | { | ||
320 | struct iwmct_priv *priv; | ||
321 | int val, ret; | ||
322 | int iosize; | ||
323 | int addr = IWMC_SDIO_INTR_GET_SIZE_ADDR; | ||
324 | struct iwmct_work_struct *read_req; | ||
325 | |||
326 | priv = sdio_get_drvdata(func); | ||
327 | |||
328 | LOG_INFO(priv, IRQ, "enter iwmct_irq\n"); | ||
329 | |||
330 | /* read the function's status register */ | ||
331 | val = sdio_readb(func, IWMC_SDIO_INTR_STATUS_ADDR, &ret); | ||
332 | |||
333 | LOG_INFO(priv, IRQ, "iir value = %d, ret=%d\n", val, ret); | ||
334 | |||
335 | if (!val) { | ||
336 | LOG_ERROR(priv, IRQ, "iir = 0, exiting ISR\n"); | ||
337 | goto exit_clear_intr; | ||
338 | } | ||
339 | |||
340 | |||
341 | /* | ||
342 | * read 2 bytes of the transaction size | ||
343 | * IMPORTANT: sdio transaction size has to be read before clearing | ||
344 | * sdio interrupt!!! | ||
345 | */ | ||
346 | val = sdio_readb(priv->func, addr++, &ret); | ||
347 | iosize = val; | ||
348 | val = sdio_readb(priv->func, addr++, &ret); | ||
349 | iosize += val << 8; | ||
350 | |||
351 | LOG_INFO(priv, IRQ, "READ size %d\n", iosize); | ||
352 | |||
353 | if (iosize == 0) { | ||
354 | LOG_ERROR(priv, IRQ, "READ size %d, exiting ISR\n", iosize); | ||
355 | goto exit_clear_intr; | ||
356 | } | ||
357 | |||
358 | /* allocate a work structure to pass iosize to the worker */ | ||
359 | read_req = kzalloc(sizeof(struct iwmct_work_struct), GFP_KERNEL); | ||
360 | if (!read_req) { | ||
361 | LOG_ERROR(priv, IRQ, "failed to allocate read_req, exit ISR\n"); | ||
362 | goto exit_clear_intr; | ||
363 | } | ||
364 | |||
365 | INIT_LIST_HEAD(&read_req->list); | ||
366 | read_req->iosize = iosize; | ||
367 | |||
368 | list_add_tail(&priv->read_req_list, &read_req->list); | ||
369 | |||
370 | /* clear the function's interrupt request bit (write 1 to clear) */ | ||
371 | sdio_writeb(func, 1, IWMC_SDIO_INTR_CLEAR_ADDR, &ret); | ||
372 | |||
373 | queue_work(priv->wq, &priv->isr_worker); | ||
374 | |||
375 | LOG_INFO(priv, IRQ, "exit iwmct_irq\n"); | ||
376 | |||
377 | return; | ||
378 | |||
379 | exit_clear_intr: | ||
380 | /* clear the function's interrupt request bit (write 1 to clear) */ | ||
381 | sdio_writeb(func, 1, IWMC_SDIO_INTR_CLEAR_ADDR, &ret); | ||
382 | } | ||
383 | |||
384 | |||
385 | static int blocks; | ||
386 | module_param(blocks, int, 0604); | ||
387 | MODULE_PARM_DESC(blocks, "max_blocks_to_send"); | ||
388 | |||
389 | static int dump; | ||
390 | module_param(dump, bool, 0604); | ||
391 | MODULE_PARM_DESC(dump, "dump_hex_content"); | ||
392 | |||
393 | static int jump = 1; | ||
394 | module_param(jump, bool, 0604); | ||
395 | |||
396 | static int direct = 1; | ||
397 | module_param(direct, bool, 0604); | ||
398 | |||
399 | static int checksum = 1; | ||
400 | module_param(checksum, bool, 0604); | ||
401 | |||
402 | static int fw_download = 1; | ||
403 | module_param(fw_download, bool, 0604); | ||
404 | |||
405 | static int block_size = IWMC_SDIO_BLK_SIZE; | ||
406 | module_param(block_size, int, 0404); | ||
407 | |||
408 | static int download_trans_blks = IWMC_DEFAULT_TR_BLK; | ||
409 | module_param(download_trans_blks, int, 0604); | ||
410 | |||
411 | static int rubbish_barker; | ||
412 | module_param(rubbish_barker, bool, 0604); | ||
413 | |||
414 | #ifdef CONFIG_IWMC3200TOP_DEBUG | ||
415 | static int log_level[LOG_SRC_MAX]; | ||
416 | static unsigned int log_level_argc; | ||
417 | module_param_array(log_level, int, &log_level_argc, 0604); | ||
418 | MODULE_PARM_DESC(log_level, "log_level"); | ||
419 | |||
420 | static int log_level_fw[FW_LOG_SRC_MAX]; | ||
421 | static unsigned int log_level_fw_argc; | ||
422 | module_param_array(log_level_fw, int, &log_level_fw_argc, 0604); | ||
423 | MODULE_PARM_DESC(log_level_fw, "log_level_fw"); | ||
424 | #endif | ||
425 | |||
426 | void iwmct_dbg_init_params(struct iwmct_priv *priv) | ||
427 | { | ||
428 | #ifdef CONFIG_IWMC3200TOP_DEBUG | ||
429 | int i; | ||
430 | |||
431 | for (i = 0; i < log_level_argc; i++) { | ||
432 | dev_notice(&priv->func->dev, "log_level[%d]=0x%X\n", | ||
433 | i, log_level[i]); | ||
434 | iwmct_log_set_filter((log_level[i] >> 8) & 0xFF, | ||
435 | log_level[i] & 0xFF); | ||
436 | } | ||
437 | for (i = 0; i < log_level_fw_argc; i++) { | ||
438 | dev_notice(&priv->func->dev, "log_level_fw[%d]=0x%X\n", | ||
439 | i, log_level_fw[i]); | ||
440 | iwmct_log_set_fw_filter((log_level_fw[i] >> 8) & 0xFF, | ||
441 | log_level_fw[i] & 0xFF); | ||
442 | } | ||
443 | #endif | ||
444 | |||
445 | priv->dbg.blocks = blocks; | ||
446 | LOG_INFO(priv, INIT, "blocks=%d\n", blocks); | ||
447 | priv->dbg.dump = (bool)dump; | ||
448 | LOG_INFO(priv, INIT, "dump=%d\n", dump); | ||
449 | priv->dbg.jump = (bool)jump; | ||
450 | LOG_INFO(priv, INIT, "jump=%d\n", jump); | ||
451 | priv->dbg.direct = (bool)direct; | ||
452 | LOG_INFO(priv, INIT, "direct=%d\n", direct); | ||
453 | priv->dbg.checksum = (bool)checksum; | ||
454 | LOG_INFO(priv, INIT, "checksum=%d\n", checksum); | ||
455 | priv->dbg.fw_download = (bool)fw_download; | ||
456 | LOG_INFO(priv, INIT, "fw_download=%d\n", fw_download); | ||
457 | priv->dbg.block_size = block_size; | ||
458 | LOG_INFO(priv, INIT, "block_size=%d\n", block_size); | ||
459 | priv->dbg.download_trans_blks = download_trans_blks; | ||
460 | LOG_INFO(priv, INIT, "download_trans_blks=%d\n", download_trans_blks); | ||
461 | } | ||
462 | |||
463 | /***************************************************************************** | ||
464 | * | ||
465 | * sysfs attributes | ||
466 | * | ||
467 | *****************************************************************************/ | ||
468 | static ssize_t show_iwmct_fw_version(struct device *d, | ||
469 | struct device_attribute *attr, char *buf) | ||
470 | { | ||
471 | struct iwmct_priv *priv = dev_get_drvdata(d); | ||
472 | return sprintf(buf, "%s\n", priv->dbg.label_fw); | ||
473 | } | ||
474 | static DEVICE_ATTR(cc_label_fw, S_IRUGO, show_iwmct_fw_version, NULL); | ||
475 | |||
476 | #ifdef CONFIG_IWMC3200TOP_DEBUG | ||
477 | static DEVICE_ATTR(log_level, S_IWUSR | S_IRUGO, | ||
478 | show_iwmct_log_level, store_iwmct_log_level); | ||
479 | static DEVICE_ATTR(log_level_fw, S_IWUSR | S_IRUGO, | ||
480 | show_iwmct_log_level_fw, store_iwmct_log_level_fw); | ||
481 | #endif | ||
482 | |||
483 | static struct attribute *iwmct_sysfs_entries[] = { | ||
484 | &dev_attr_cc_label_fw.attr, | ||
485 | #ifdef CONFIG_IWMC3200TOP_DEBUG | ||
486 | &dev_attr_log_level.attr, | ||
487 | &dev_attr_log_level_fw.attr, | ||
488 | #endif | ||
489 | NULL | ||
490 | }; | ||
491 | |||
492 | static struct attribute_group iwmct_attribute_group = { | ||
493 | .name = NULL, /* put in device directory */ | ||
494 | .attrs = iwmct_sysfs_entries, | ||
495 | }; | ||
496 | |||
497 | |||
498 | static int iwmct_probe(struct sdio_func *func, | ||
499 | const struct sdio_device_id *id) | ||
500 | { | ||
501 | struct iwmct_priv *priv; | ||
502 | int ret; | ||
503 | int val = 1; | ||
504 | int addr = IWMC_SDIO_INTR_ENABLE_ADDR; | ||
505 | |||
506 | dev_dbg(&func->dev, "enter iwmct_probe\n"); | ||
507 | |||
508 | dev_dbg(&func->dev, "IRQ polling period id %u msecs, HZ is %d\n", | ||
509 | jiffies_to_msecs(2147483647), HZ); | ||
510 | |||
511 | priv = kzalloc(sizeof(struct iwmct_priv), GFP_KERNEL); | ||
512 | if (!priv) { | ||
513 | dev_err(&func->dev, "kzalloc error\n"); | ||
514 | return -ENOMEM; | ||
515 | } | ||
516 | priv->func = func; | ||
517 | sdio_set_drvdata(func, priv); | ||
518 | |||
519 | |||
520 | /* create drivers work queue */ | ||
521 | priv->wq = create_workqueue(DRV_NAME "_wq"); | ||
522 | priv->bus_rescan_wq = create_workqueue(DRV_NAME "_rescan_wq"); | ||
523 | INIT_WORK(&priv->bus_rescan_worker, iwmct_rescan_worker); | ||
524 | INIT_WORK(&priv->isr_worker, iwmct_irq_read_worker); | ||
525 | |||
526 | init_waitqueue_head(&priv->wait_q); | ||
527 | |||
528 | sdio_claim_host(func); | ||
529 | /* FIXME: Remove after it is fixed in the Boot ROM upgrade */ | ||
530 | func->enable_timeout = 10; | ||
531 | |||
532 | /* In our HW, setting the block size also wakes up the boot rom. */ | ||
533 | ret = sdio_set_block_size(func, priv->dbg.block_size); | ||
534 | if (ret) { | ||
535 | LOG_ERROR(priv, INIT, | ||
536 | "sdio_set_block_size() failure: %d\n", ret); | ||
537 | goto error_sdio_enable; | ||
538 | } | ||
539 | |||
540 | ret = sdio_enable_func(func); | ||
541 | if (ret) { | ||
542 | LOG_ERROR(priv, INIT, "sdio_enable_func() failure: %d\n", ret); | ||
543 | goto error_sdio_enable; | ||
544 | } | ||
545 | |||
546 | /* init reset and dev_sync states */ | ||
547 | atomic_set(&priv->reset, 0); | ||
548 | atomic_set(&priv->dev_sync, 0); | ||
549 | |||
550 | /* init read req queue */ | ||
551 | INIT_LIST_HEAD(&priv->read_req_list); | ||
552 | |||
553 | /* process configurable parameters */ | ||
554 | iwmct_dbg_init_params(priv); | ||
555 | ret = sysfs_create_group(&func->dev.kobj, &iwmct_attribute_group); | ||
556 | if (ret) { | ||
557 | LOG_ERROR(priv, INIT, "Failed to register attributes and " | ||
558 | "initialize module_params\n"); | ||
559 | goto error_dev_attrs; | ||
560 | } | ||
561 | |||
562 | iwmct_dbgfs_register(priv, DRV_NAME); | ||
563 | |||
564 | if (!priv->dbg.direct && priv->dbg.download_trans_blks > 8) { | ||
565 | LOG_INFO(priv, INIT, | ||
566 | "Reducing transaction to 8 blocks = 2K (from %d)\n", | ||
567 | priv->dbg.download_trans_blks); | ||
568 | priv->dbg.download_trans_blks = 8; | ||
569 | } | ||
570 | priv->trans_len = priv->dbg.download_trans_blks * priv->dbg.block_size; | ||
571 | LOG_INFO(priv, INIT, "Transaction length = %d\n", priv->trans_len); | ||
572 | |||
573 | ret = sdio_claim_irq(func, iwmct_irq); | ||
574 | if (ret) { | ||
575 | LOG_ERROR(priv, INIT, "sdio_claim_irq() failure: %d\n", ret); | ||
576 | goto error_claim_irq; | ||
577 | } | ||
578 | |||
579 | |||
580 | /* Enable function's interrupt */ | ||
581 | sdio_writeb(priv->func, val, addr, &ret); | ||
582 | if (ret) { | ||
583 | LOG_ERROR(priv, INIT, "Failure writing to " | ||
584 | "Interrupt Enable Register (%d): %d\n", addr, ret); | ||
585 | goto error_enable_int; | ||
586 | } | ||
587 | |||
588 | sdio_release_host(func); | ||
589 | |||
590 | LOG_INFO(priv, INIT, "exit iwmct_probe\n"); | ||
591 | |||
592 | return ret; | ||
593 | |||
594 | error_enable_int: | ||
595 | sdio_release_irq(func); | ||
596 | error_claim_irq: | ||
597 | sdio_disable_func(func); | ||
598 | error_dev_attrs: | ||
599 | iwmct_dbgfs_unregister(priv->dbgfs); | ||
600 | sysfs_remove_group(&func->dev.kobj, &iwmct_attribute_group); | ||
601 | error_sdio_enable: | ||
602 | sdio_release_host(func); | ||
603 | return ret; | ||
604 | } | ||
605 | |||
606 | static void iwmct_remove(struct sdio_func *func) | ||
607 | { | ||
608 | struct iwmct_work_struct *read_req; | ||
609 | struct iwmct_priv *priv = sdio_get_drvdata(func); | ||
610 | |||
611 | priv = sdio_get_drvdata(func); | ||
612 | |||
613 | LOG_INFO(priv, INIT, "enter\n"); | ||
614 | |||
615 | sdio_claim_host(func); | ||
616 | sdio_release_irq(func); | ||
617 | sdio_release_host(func); | ||
618 | |||
619 | /* Safely destroy osc workqueue */ | ||
620 | destroy_workqueue(priv->bus_rescan_wq); | ||
621 | destroy_workqueue(priv->wq); | ||
622 | |||
623 | sdio_claim_host(func); | ||
624 | sdio_disable_func(func); | ||
625 | sysfs_remove_group(&func->dev.kobj, &iwmct_attribute_group); | ||
626 | iwmct_dbgfs_unregister(priv->dbgfs); | ||
627 | sdio_release_host(func); | ||
628 | |||
629 | /* free read requests */ | ||
630 | while (!list_empty(&priv->read_req_list)) { | ||
631 | read_req = list_entry(priv->read_req_list.next, | ||
632 | struct iwmct_work_struct, list); | ||
633 | |||
634 | list_del(&read_req->list); | ||
635 | kfree(read_req); | ||
636 | } | ||
637 | |||
638 | kfree(priv); | ||
639 | } | ||
640 | |||
641 | |||
642 | static const struct sdio_device_id iwmct_ids[] = { | ||
643 | /* Intel Wireless MultiCom 3200 Top Driver */ | ||
644 | { SDIO_DEVICE(SDIO_VENDOR_ID_INTEL, 0x1404)}, | ||
645 | { }, /* Terminating entry */ | ||
646 | }; | ||
647 | |||
648 | MODULE_DEVICE_TABLE(sdio, iwmct_ids); | ||
649 | |||
650 | static struct sdio_driver iwmct_driver = { | ||
651 | .probe = iwmct_probe, | ||
652 | .remove = iwmct_remove, | ||
653 | .name = DRV_NAME, | ||
654 | .id_table = iwmct_ids, | ||
655 | }; | ||
656 | |||
657 | static int __init iwmct_init(void) | ||
658 | { | ||
659 | int rc; | ||
660 | |||
661 | /* Default log filter settings */ | ||
662 | iwmct_log_set_filter(LOG_SRC_ALL, LOG_SEV_FILTER_RUNTIME); | ||
663 | iwmct_log_set_filter(LOG_SRC_FW_MSG, LOG_SEV_FILTER_ALL); | ||
664 | iwmct_log_set_fw_filter(LOG_SRC_ALL, FW_LOG_SEV_FILTER_RUNTIME); | ||
665 | |||
666 | rc = sdio_register_driver(&iwmct_driver); | ||
667 | |||
668 | return rc; | ||
669 | } | ||
670 | |||
671 | static void __exit iwmct_exit(void) | ||
672 | { | ||
673 | sdio_unregister_driver(&iwmct_driver); | ||
674 | } | ||
675 | |||
676 | module_init(iwmct_init); | ||
677 | module_exit(iwmct_exit); | ||
678 | |||