]>
Commit | Line | Data |
---|---|---|
ab69a5ae TW |
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> | |
5a0e3ad6 | 28 | #include <linux/slab.h> |
ab69a5ae TW |
29 | #include <linux/init.h> |
30 | #include <linux/kernel.h> | |
31 | #include <linux/debugfs.h> | |
32 | #include <linux/mmc/sdio_ids.h> | |
33 | #include <linux/mmc/sdio_func.h> | |
34 | #include <linux/mmc/sdio.h> | |
35 | ||
36 | #include "iwmc3200top.h" | |
37 | #include "log.h" | |
38 | #include "fw-msg.h" | |
39 | #include "debugfs.h" | |
40 | ||
41 | ||
42 | #define DRIVER_DESCRIPTION "Intel(R) IWMC 3200 Top Driver" | |
43 | #define DRIVER_COPYRIGHT "Copyright (c) 2008 Intel Corporation." | |
44 | ||
cb43e234 | 45 | #define DRIVER_VERSION "0.1.62" |
ab69a5ae TW |
46 | |
47 | MODULE_DESCRIPTION(DRIVER_DESCRIPTION); | |
48 | MODULE_VERSION(DRIVER_VERSION); | |
49 | MODULE_LICENSE("GPL"); | |
50 | MODULE_AUTHOR(DRIVER_COPYRIGHT); | |
0e481747 | 51 | MODULE_FIRMWARE(FW_NAME(FW_API_VER)); |
ab69a5ae | 52 | |
fe45332e TW |
53 | |
54 | static inline int __iwmct_tx(struct iwmct_priv *priv, void *src, int count) | |
55 | { | |
56 | return sdio_memcpy_toio(priv->func, IWMC_SDIO_DATA_ADDR, src, count); | |
57 | ||
58 | } | |
59 | int iwmct_tx(struct iwmct_priv *priv, void *src, int count) | |
60 | { | |
61 | int ret; | |
62 | sdio_claim_host(priv->func); | |
63 | ret = __iwmct_tx(priv, src, count); | |
64 | sdio_release_host(priv->func); | |
65 | return ret; | |
66 | } | |
ab69a5ae TW |
67 | /* |
68 | * This workers main task is to wait for OP_OPR_ALIVE | |
69 | * from TOP FW until ALIVE_MSG_TIMOUT timeout is elapsed. | |
70 | * When OP_OPR_ALIVE received it will issue | |
71 | * a call to "bus_rescan_devices". | |
72 | */ | |
73 | static void iwmct_rescan_worker(struct work_struct *ws) | |
74 | { | |
75 | struct iwmct_priv *priv; | |
76 | int ret; | |
77 | ||
78 | priv = container_of(ws, struct iwmct_priv, bus_rescan_worker); | |
79 | ||
80 | LOG_INFO(priv, FW_MSG, "Calling bus_rescan\n"); | |
81 | ||
82 | ret = bus_rescan_devices(priv->func->dev.bus); | |
83 | if (ret < 0) | |
0df828f6 | 84 | LOG_INFO(priv, INIT, "bus_rescan_devices FAILED!!!\n"); |
ab69a5ae TW |
85 | } |
86 | ||
87 | static void op_top_message(struct iwmct_priv *priv, struct top_msg *msg) | |
88 | { | |
89 | switch (msg->hdr.opcode) { | |
90 | case OP_OPR_ALIVE: | |
91 | LOG_INFO(priv, FW_MSG, "Got ALIVE from device, wake rescan\n"); | |
51f50f81 | 92 | schedule_work(&priv->bus_rescan_worker); |
ab69a5ae TW |
93 | break; |
94 | default: | |
95 | LOG_INFO(priv, FW_MSG, "Received msg opcode 0x%X\n", | |
96 | msg->hdr.opcode); | |
97 | break; | |
98 | } | |
99 | } | |
100 | ||
101 | ||
102 | static void handle_top_message(struct iwmct_priv *priv, u8 *buf, int len) | |
103 | { | |
104 | struct top_msg *msg; | |
105 | ||
106 | msg = (struct top_msg *)buf; | |
107 | ||
108 | if (msg->hdr.type != COMM_TYPE_D2H) { | |
109 | LOG_ERROR(priv, FW_MSG, | |
110 | "Message from TOP with invalid message type 0x%X\n", | |
111 | msg->hdr.type); | |
112 | return; | |
113 | } | |
114 | ||
115 | if (len < sizeof(msg->hdr)) { | |
116 | LOG_ERROR(priv, FW_MSG, | |
117 | "Message from TOP is too short for message header " | |
118 | "received %d bytes, expected at least %zd bytes\n", | |
119 | len, sizeof(msg->hdr)); | |
120 | return; | |
121 | } | |
122 | ||
123 | if (len < le16_to_cpu(msg->hdr.length) + sizeof(msg->hdr)) { | |
124 | LOG_ERROR(priv, FW_MSG, | |
125 | "Message length (%d bytes) is shorter than " | |
126 | "in header (%d bytes)\n", | |
127 | len, le16_to_cpu(msg->hdr.length)); | |
128 | return; | |
129 | } | |
130 | ||
131 | switch (msg->hdr.category) { | |
132 | case COMM_CATEGORY_OPERATIONAL: | |
133 | op_top_message(priv, (struct top_msg *)buf); | |
134 | break; | |
135 | ||
136 | case COMM_CATEGORY_DEBUG: | |
137 | case COMM_CATEGORY_TESTABILITY: | |
138 | case COMM_CATEGORY_DIAGNOSTICS: | |
139 | iwmct_log_top_message(priv, buf, len); | |
140 | break; | |
141 | ||
142 | default: | |
143 | LOG_ERROR(priv, FW_MSG, | |
144 | "Message from TOP with unknown category 0x%X\n", | |
145 | msg->hdr.category); | |
146 | break; | |
147 | } | |
148 | } | |
149 | ||
150 | int iwmct_send_hcmd(struct iwmct_priv *priv, u8 *cmd, u16 len) | |
151 | { | |
152 | int ret; | |
153 | u8 *buf; | |
154 | ||
0df828f6 | 155 | LOG_TRACE(priv, FW_MSG, "Sending hcmd:\n"); |
ab69a5ae TW |
156 | |
157 | /* add padding to 256 for IWMC */ | |
158 | ((struct top_msg *)cmd)->hdr.flags |= CMD_FLAG_PADDING_256; | |
159 | ||
160 | LOG_HEXDUMP(FW_MSG, cmd, len); | |
161 | ||
162 | if (len > FW_HCMD_BLOCK_SIZE) { | |
163 | LOG_ERROR(priv, FW_MSG, "size %d exceeded hcmd max size %d\n", | |
164 | len, FW_HCMD_BLOCK_SIZE); | |
165 | return -1; | |
166 | } | |
167 | ||
168 | buf = kzalloc(FW_HCMD_BLOCK_SIZE, GFP_KERNEL); | |
169 | if (!buf) { | |
170 | LOG_ERROR(priv, FW_MSG, "kzalloc error, buf size %d\n", | |
171 | FW_HCMD_BLOCK_SIZE); | |
172 | return -1; | |
173 | } | |
174 | ||
175 | memcpy(buf, cmd, len); | |
fe45332e | 176 | ret = iwmct_tx(priv, buf, FW_HCMD_BLOCK_SIZE); |
ab69a5ae TW |
177 | |
178 | kfree(buf); | |
179 | return ret; | |
180 | } | |
181 | ||
ab69a5ae TW |
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 | ||
0df828f6 | 195 | LOG_TRACE(priv, IRQ, "enter iwmct_irq_read_worker %p\n", ws); |
ab69a5ae TW |
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 */ | |
25985edc | 271 | LOG_INFO(priv, IRQ, "Received reboot barker: %x\n", barker); |
ab69a5ae TW |
272 | priv->barker = barker; |
273 | ||
274 | if (barker & BARKER_DNLOAD_SYNC_MSK) { | |
275 | /* Send the same barker back */ | |
fe45332e | 276 | ret = __iwmct_tx(priv, buf, iosize); |
ab69a5ae TW |
277 | if (ret) { |
278 | LOG_ERROR(priv, IRQ, | |
279 | "error %d echoing barker\n", ret); | |
280 | goto exit_release; | |
281 | } | |
282 | LOG_INFO(priv, IRQ, "Echoing barker to device\n"); | |
283 | atomic_set(&priv->dev_sync, 1); | |
284 | goto exit_release; | |
285 | } | |
286 | ||
287 | /* Continuing to FW download (without Sync) */ | |
288 | LOG_INFO(priv, IRQ, "No sync requested " | |
289 | "- starting FW download\n"); | |
290 | } | |
291 | ||
292 | sdio_release_host(priv->func); | |
293 | ||
ab69a5ae TW |
294 | if (priv->dbg.fw_download) |
295 | iwmct_fw_load(priv); | |
296 | else | |
297 | LOG_ERROR(priv, IRQ, "FW download not allowed\n"); | |
298 | ||
299 | goto exit; | |
300 | ||
301 | exit_release: | |
302 | sdio_release_host(priv->func); | |
303 | exit: | |
304 | kfree(buf); | |
0df828f6 | 305 | LOG_TRACE(priv, IRQ, "exit iwmct_irq_read_worker\n"); |
ab69a5ae TW |
306 | } |
307 | ||
308 | static void iwmct_irq(struct sdio_func *func) | |
309 | { | |
310 | struct iwmct_priv *priv; | |
311 | int val, ret; | |
312 | int iosize; | |
313 | int addr = IWMC_SDIO_INTR_GET_SIZE_ADDR; | |
314 | struct iwmct_work_struct *read_req; | |
315 | ||
316 | priv = sdio_get_drvdata(func); | |
317 | ||
0df828f6 | 318 | LOG_TRACE(priv, IRQ, "enter iwmct_irq\n"); |
ab69a5ae TW |
319 | |
320 | /* read the function's status register */ | |
321 | val = sdio_readb(func, IWMC_SDIO_INTR_STATUS_ADDR, &ret); | |
322 | ||
0df828f6 | 323 | LOG_TRACE(priv, IRQ, "iir value = %d, ret=%d\n", val, ret); |
ab69a5ae TW |
324 | |
325 | if (!val) { | |
326 | LOG_ERROR(priv, IRQ, "iir = 0, exiting ISR\n"); | |
327 | goto exit_clear_intr; | |
328 | } | |
329 | ||
330 | ||
331 | /* | |
332 | * read 2 bytes of the transaction size | |
333 | * IMPORTANT: sdio transaction size has to be read before clearing | |
334 | * sdio interrupt!!! | |
335 | */ | |
336 | val = sdio_readb(priv->func, addr++, &ret); | |
337 | iosize = val; | |
338 | val = sdio_readb(priv->func, addr++, &ret); | |
339 | iosize += val << 8; | |
340 | ||
341 | LOG_INFO(priv, IRQ, "READ size %d\n", iosize); | |
342 | ||
343 | if (iosize == 0) { | |
344 | LOG_ERROR(priv, IRQ, "READ size %d, exiting ISR\n", iosize); | |
345 | goto exit_clear_intr; | |
346 | } | |
347 | ||
348 | /* allocate a work structure to pass iosize to the worker */ | |
349 | read_req = kzalloc(sizeof(struct iwmct_work_struct), GFP_KERNEL); | |
350 | if (!read_req) { | |
351 | LOG_ERROR(priv, IRQ, "failed to allocate read_req, exit ISR\n"); | |
352 | goto exit_clear_intr; | |
353 | } | |
354 | ||
355 | INIT_LIST_HEAD(&read_req->list); | |
356 | read_req->iosize = iosize; | |
357 | ||
358 | list_add_tail(&priv->read_req_list, &read_req->list); | |
359 | ||
360 | /* clear the function's interrupt request bit (write 1 to clear) */ | |
361 | sdio_writeb(func, 1, IWMC_SDIO_INTR_CLEAR_ADDR, &ret); | |
362 | ||
51f50f81 | 363 | schedule_work(&priv->isr_worker); |
ab69a5ae | 364 | |
0df828f6 | 365 | LOG_TRACE(priv, IRQ, "exit iwmct_irq\n"); |
ab69a5ae TW |
366 | |
367 | return; | |
368 | ||
369 | exit_clear_intr: | |
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 | ||
374 | ||
375 | static int blocks; | |
376 | module_param(blocks, int, 0604); | |
377 | MODULE_PARM_DESC(blocks, "max_blocks_to_send"); | |
378 | ||
90ab5ee9 | 379 | static bool dump; |
ab69a5ae TW |
380 | module_param(dump, bool, 0604); |
381 | MODULE_PARM_DESC(dump, "dump_hex_content"); | |
382 | ||
90ab5ee9 | 383 | static bool jump = 1; |
ab69a5ae TW |
384 | module_param(jump, bool, 0604); |
385 | ||
90ab5ee9 | 386 | static bool direct = 1; |
ab69a5ae TW |
387 | module_param(direct, bool, 0604); |
388 | ||
90ab5ee9 | 389 | static bool checksum = 1; |
ab69a5ae TW |
390 | module_param(checksum, bool, 0604); |
391 | ||
90ab5ee9 | 392 | static bool fw_download = 1; |
ab69a5ae TW |
393 | module_param(fw_download, bool, 0604); |
394 | ||
395 | static int block_size = IWMC_SDIO_BLK_SIZE; | |
396 | module_param(block_size, int, 0404); | |
397 | ||
398 | static int download_trans_blks = IWMC_DEFAULT_TR_BLK; | |
399 | module_param(download_trans_blks, int, 0604); | |
400 | ||
90ab5ee9 | 401 | static bool rubbish_barker; |
ab69a5ae TW |
402 | module_param(rubbish_barker, bool, 0604); |
403 | ||
404 | #ifdef CONFIG_IWMC3200TOP_DEBUG | |
405 | static int log_level[LOG_SRC_MAX]; | |
406 | static unsigned int log_level_argc; | |
407 | module_param_array(log_level, int, &log_level_argc, 0604); | |
408 | MODULE_PARM_DESC(log_level, "log_level"); | |
409 | ||
410 | static int log_level_fw[FW_LOG_SRC_MAX]; | |
411 | static unsigned int log_level_fw_argc; | |
412 | module_param_array(log_level_fw, int, &log_level_fw_argc, 0604); | |
413 | MODULE_PARM_DESC(log_level_fw, "log_level_fw"); | |
414 | #endif | |
415 | ||
416 | void iwmct_dbg_init_params(struct iwmct_priv *priv) | |
417 | { | |
418 | #ifdef CONFIG_IWMC3200TOP_DEBUG | |
419 | int i; | |
420 | ||
421 | for (i = 0; i < log_level_argc; i++) { | |
422 | dev_notice(&priv->func->dev, "log_level[%d]=0x%X\n", | |
423 | i, log_level[i]); | |
424 | iwmct_log_set_filter((log_level[i] >> 8) & 0xFF, | |
425 | log_level[i] & 0xFF); | |
426 | } | |
427 | for (i = 0; i < log_level_fw_argc; i++) { | |
428 | dev_notice(&priv->func->dev, "log_level_fw[%d]=0x%X\n", | |
429 | i, log_level_fw[i]); | |
430 | iwmct_log_set_fw_filter((log_level_fw[i] >> 8) & 0xFF, | |
431 | log_level_fw[i] & 0xFF); | |
432 | } | |
433 | #endif | |
434 | ||
435 | priv->dbg.blocks = blocks; | |
436 | LOG_INFO(priv, INIT, "blocks=%d\n", blocks); | |
437 | priv->dbg.dump = (bool)dump; | |
438 | LOG_INFO(priv, INIT, "dump=%d\n", dump); | |
439 | priv->dbg.jump = (bool)jump; | |
440 | LOG_INFO(priv, INIT, "jump=%d\n", jump); | |
441 | priv->dbg.direct = (bool)direct; | |
442 | LOG_INFO(priv, INIT, "direct=%d\n", direct); | |
443 | priv->dbg.checksum = (bool)checksum; | |
444 | LOG_INFO(priv, INIT, "checksum=%d\n", checksum); | |
445 | priv->dbg.fw_download = (bool)fw_download; | |
446 | LOG_INFO(priv, INIT, "fw_download=%d\n", fw_download); | |
447 | priv->dbg.block_size = block_size; | |
448 | LOG_INFO(priv, INIT, "block_size=%d\n", block_size); | |
449 | priv->dbg.download_trans_blks = download_trans_blks; | |
450 | LOG_INFO(priv, INIT, "download_trans_blks=%d\n", download_trans_blks); | |
451 | } | |
452 | ||
453 | /***************************************************************************** | |
454 | * | |
455 | * sysfs attributes | |
456 | * | |
457 | *****************************************************************************/ | |
458 | static ssize_t show_iwmct_fw_version(struct device *d, | |
459 | struct device_attribute *attr, char *buf) | |
460 | { | |
461 | struct iwmct_priv *priv = dev_get_drvdata(d); | |
462 | return sprintf(buf, "%s\n", priv->dbg.label_fw); | |
463 | } | |
464 | static DEVICE_ATTR(cc_label_fw, S_IRUGO, show_iwmct_fw_version, NULL); | |
465 | ||
466 | #ifdef CONFIG_IWMC3200TOP_DEBUG | |
467 | static DEVICE_ATTR(log_level, S_IWUSR | S_IRUGO, | |
468 | show_iwmct_log_level, store_iwmct_log_level); | |
469 | static DEVICE_ATTR(log_level_fw, S_IWUSR | S_IRUGO, | |
470 | show_iwmct_log_level_fw, store_iwmct_log_level_fw); | |
471 | #endif | |
472 | ||
473 | static struct attribute *iwmct_sysfs_entries[] = { | |
474 | &dev_attr_cc_label_fw.attr, | |
475 | #ifdef CONFIG_IWMC3200TOP_DEBUG | |
476 | &dev_attr_log_level.attr, | |
477 | &dev_attr_log_level_fw.attr, | |
478 | #endif | |
479 | NULL | |
480 | }; | |
481 | ||
482 | static struct attribute_group iwmct_attribute_group = { | |
483 | .name = NULL, /* put in device directory */ | |
484 | .attrs = iwmct_sysfs_entries, | |
485 | }; | |
486 | ||
487 | ||
488 | static int iwmct_probe(struct sdio_func *func, | |
489 | const struct sdio_device_id *id) | |
490 | { | |
491 | struct iwmct_priv *priv; | |
492 | int ret; | |
493 | int val = 1; | |
494 | int addr = IWMC_SDIO_INTR_ENABLE_ADDR; | |
495 | ||
496 | dev_dbg(&func->dev, "enter iwmct_probe\n"); | |
497 | ||
498 | dev_dbg(&func->dev, "IRQ polling period id %u msecs, HZ is %d\n", | |
499 | jiffies_to_msecs(2147483647), HZ); | |
500 | ||
501 | priv = kzalloc(sizeof(struct iwmct_priv), GFP_KERNEL); | |
502 | if (!priv) { | |
503 | dev_err(&func->dev, "kzalloc error\n"); | |
504 | return -ENOMEM; | |
505 | } | |
506 | priv->func = func; | |
507 | sdio_set_drvdata(func, priv); | |
508 | ||
ab69a5ae TW |
509 | INIT_WORK(&priv->bus_rescan_worker, iwmct_rescan_worker); |
510 | INIT_WORK(&priv->isr_worker, iwmct_irq_read_worker); | |
511 | ||
512 | init_waitqueue_head(&priv->wait_q); | |
513 | ||
514 | sdio_claim_host(func); | |
515 | /* FIXME: Remove after it is fixed in the Boot ROM upgrade */ | |
516 | func->enable_timeout = 10; | |
517 | ||
518 | /* In our HW, setting the block size also wakes up the boot rom. */ | |
519 | ret = sdio_set_block_size(func, priv->dbg.block_size); | |
520 | if (ret) { | |
521 | LOG_ERROR(priv, INIT, | |
522 | "sdio_set_block_size() failure: %d\n", ret); | |
523 | goto error_sdio_enable; | |
524 | } | |
525 | ||
526 | ret = sdio_enable_func(func); | |
527 | if (ret) { | |
528 | LOG_ERROR(priv, INIT, "sdio_enable_func() failure: %d\n", ret); | |
529 | goto error_sdio_enable; | |
530 | } | |
531 | ||
532 | /* init reset and dev_sync states */ | |
533 | atomic_set(&priv->reset, 0); | |
534 | atomic_set(&priv->dev_sync, 0); | |
535 | ||
536 | /* init read req queue */ | |
537 | INIT_LIST_HEAD(&priv->read_req_list); | |
538 | ||
539 | /* process configurable parameters */ | |
540 | iwmct_dbg_init_params(priv); | |
541 | ret = sysfs_create_group(&func->dev.kobj, &iwmct_attribute_group); | |
542 | if (ret) { | |
543 | LOG_ERROR(priv, INIT, "Failed to register attributes and " | |
544 | "initialize module_params\n"); | |
545 | goto error_dev_attrs; | |
546 | } | |
547 | ||
548 | iwmct_dbgfs_register(priv, DRV_NAME); | |
549 | ||
550 | if (!priv->dbg.direct && priv->dbg.download_trans_blks > 8) { | |
551 | LOG_INFO(priv, INIT, | |
552 | "Reducing transaction to 8 blocks = 2K (from %d)\n", | |
553 | priv->dbg.download_trans_blks); | |
554 | priv->dbg.download_trans_blks = 8; | |
555 | } | |
556 | priv->trans_len = priv->dbg.download_trans_blks * priv->dbg.block_size; | |
557 | LOG_INFO(priv, INIT, "Transaction length = %d\n", priv->trans_len); | |
558 | ||
559 | ret = sdio_claim_irq(func, iwmct_irq); | |
560 | if (ret) { | |
561 | LOG_ERROR(priv, INIT, "sdio_claim_irq() failure: %d\n", ret); | |
562 | goto error_claim_irq; | |
563 | } | |
564 | ||
565 | ||
566 | /* Enable function's interrupt */ | |
567 | sdio_writeb(priv->func, val, addr, &ret); | |
568 | if (ret) { | |
569 | LOG_ERROR(priv, INIT, "Failure writing to " | |
570 | "Interrupt Enable Register (%d): %d\n", addr, ret); | |
571 | goto error_enable_int; | |
572 | } | |
573 | ||
574 | sdio_release_host(func); | |
575 | ||
576 | LOG_INFO(priv, INIT, "exit iwmct_probe\n"); | |
577 | ||
578 | return ret; | |
579 | ||
580 | error_enable_int: | |
581 | sdio_release_irq(func); | |
582 | error_claim_irq: | |
583 | sdio_disable_func(func); | |
584 | error_dev_attrs: | |
585 | iwmct_dbgfs_unregister(priv->dbgfs); | |
586 | sysfs_remove_group(&func->dev.kobj, &iwmct_attribute_group); | |
587 | error_sdio_enable: | |
588 | sdio_release_host(func); | |
589 | return ret; | |
590 | } | |
591 | ||
592 | static void iwmct_remove(struct sdio_func *func) | |
593 | { | |
594 | struct iwmct_work_struct *read_req; | |
595 | struct iwmct_priv *priv = sdio_get_drvdata(func); | |
596 | ||
ab69a5ae TW |
597 | LOG_INFO(priv, INIT, "enter\n"); |
598 | ||
599 | sdio_claim_host(func); | |
600 | sdio_release_irq(func); | |
601 | sdio_release_host(func); | |
602 | ||
51f50f81 TH |
603 | /* Make sure works are finished */ |
604 | flush_work_sync(&priv->bus_rescan_worker); | |
605 | flush_work_sync(&priv->isr_worker); | |
ab69a5ae TW |
606 | |
607 | sdio_claim_host(func); | |
608 | sdio_disable_func(func); | |
609 | sysfs_remove_group(&func->dev.kobj, &iwmct_attribute_group); | |
610 | iwmct_dbgfs_unregister(priv->dbgfs); | |
611 | sdio_release_host(func); | |
612 | ||
613 | /* free read requests */ | |
614 | while (!list_empty(&priv->read_req_list)) { | |
615 | read_req = list_entry(priv->read_req_list.next, | |
616 | struct iwmct_work_struct, list); | |
617 | ||
618 | list_del(&read_req->list); | |
619 | kfree(read_req); | |
620 | } | |
621 | ||
622 | kfree(priv); | |
623 | } | |
624 | ||
625 | ||
626 | static const struct sdio_device_id iwmct_ids[] = { | |
11e25217 TW |
627 | /* Intel Wireless MultiCom 3200 Top Driver */ |
628 | { SDIO_DEVICE(SDIO_VENDOR_ID_INTEL, 0x1404)}, | |
629 | { }, /* Terminating entry */ | |
ab69a5ae TW |
630 | }; |
631 | ||
632 | MODULE_DEVICE_TABLE(sdio, iwmct_ids); | |
633 | ||
634 | static struct sdio_driver iwmct_driver = { | |
635 | .probe = iwmct_probe, | |
636 | .remove = iwmct_remove, | |
637 | .name = DRV_NAME, | |
638 | .id_table = iwmct_ids, | |
639 | }; | |
640 | ||
641 | static int __init iwmct_init(void) | |
642 | { | |
643 | int rc; | |
644 | ||
645 | /* Default log filter settings */ | |
646 | iwmct_log_set_filter(LOG_SRC_ALL, LOG_SEV_FILTER_RUNTIME); | |
0df828f6 | 647 | iwmct_log_set_filter(LOG_SRC_FW_MSG, LOG_SEV_FW_FILTER_ALL); |
ab69a5ae TW |
648 | iwmct_log_set_fw_filter(LOG_SRC_ALL, FW_LOG_SEV_FILTER_RUNTIME); |
649 | ||
650 | rc = sdio_register_driver(&iwmct_driver); | |
651 | ||
652 | return rc; | |
653 | } | |
654 | ||
655 | static void __exit iwmct_exit(void) | |
656 | { | |
657 | sdio_unregister_driver(&iwmct_driver); | |
658 | } | |
659 | ||
660 | module_init(iwmct_init); | |
661 | module_exit(iwmct_exit); | |
662 |