]> Pileus Git - ~andy/linux/blobdiff - drivers/net/wireless/libertas/if_sdio.c
libertas: fix card cleanup order in SDIO driver
[~andy/linux] / drivers / net / wireless / libertas / if_sdio.c
index 76f4c653d6419782caf65902ffed19b3a5153f03..485a8d406525ccbea266cfe155cca8150ccdf967 100644 (file)
 #include "decl.h"
 #include "defs.h"
 #include "dev.h"
+#include "cmd.h"
 #include "if_sdio.h"
 
+/* The if_sdio_remove() callback function is called when
+ * user removes this module from kernel space or ejects
+ * the card from the slot. The driver handles these 2 cases
+ * differently for SD8688 combo chip.
+ * If the user is removing the module, the FUNC_SHUTDOWN
+ * command for SD8688 is sent to the firmware.
+ * If the card is removed, there is no need to send this command.
+ *
+ * The variable 'user_rmmod' is used to distinguish these two
+ * scenarios. This flag is initialized as FALSE in case the card
+ * is removed, and will be set to TRUE for module removal when
+ * module_exit function is called.
+ */
+static u8 user_rmmod;
+
 static char *lbs_helper_name = NULL;
 module_param_named(helper_name, lbs_helper_name, charp, 0644);
 
@@ -48,8 +64,11 @@ static char *lbs_fw_name = NULL;
 module_param_named(fw_name, lbs_fw_name, charp, 0644);
 
 static const struct sdio_device_id if_sdio_ids[] = {
-       { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_LIBERTAS) },
-       { /* end: all zeroes */                                         },
+       { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL,
+                       SDIO_DEVICE_ID_MARVELL_LIBERTAS) },
+       { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL,
+                       SDIO_DEVICE_ID_MARVELL_8688WLAN) },
+       { /* end: all zeroes */                         },
 };
 
 MODULE_DEVICE_TABLE(sdio, if_sdio_ids);
@@ -63,16 +82,22 @@ struct if_sdio_model {
 static struct if_sdio_model if_sdio_models[] = {
        {
                /* 8385 */
-               .model = 0x04,
+               .model = IF_SDIO_MODEL_8385,
                .helper = "sd8385_helper.bin",
                .firmware = "sd8385.bin",
        },
        {
                /* 8686 */
-               .model = 0x0B,
+               .model = IF_SDIO_MODEL_8686,
                .helper = "sd8686_helper.bin",
                .firmware = "sd8686.bin",
        },
+       {
+               /* 8688 */
+               .model = IF_SDIO_MODEL_8688,
+               .helper = "sd8688_helper.bin",
+               .firmware = "sd8688.bin",
+       },
 };
 
 struct if_sdio_packet {
@@ -87,6 +112,7 @@ struct if_sdio_card {
 
        int                     model;
        unsigned long           ioport;
+       unsigned int            scratch_reg;
 
        const char              *helper;
        const char              *firmware;
@@ -98,25 +124,29 @@ struct if_sdio_card {
 
        struct workqueue_struct *workqueue;
        struct work_struct      packet_worker;
+
+       u8                      rx_unit;
 };
 
 /********************************************************************/
 /* I/O                                                              */
 /********************************************************************/
 
+/*
+ *  For SD8385/SD8686, this function reads firmware status after
+ *  the image is downloaded, or reads RX packet length when
+ *  interrupt (with IF_SDIO_H_INT_UPLD bit set) is received.
+ *  For SD8688, this function reads firmware status only.
+ */
 static u16 if_sdio_read_scratch(struct if_sdio_card *card, int *err)
 {
-       int ret, reg;
+       int ret;
        u16 scratch;
 
-       if (card->model == 0x04)
-               reg = IF_SDIO_SCRATCH_OLD;
-       else
-               reg = IF_SDIO_SCRATCH;
-
-       scratch = sdio_readb(card->func, reg, &ret);
+       scratch = sdio_readb(card->func, card->scratch_reg, &ret);
        if (!ret)
-               scratch |= sdio_readb(card->func, reg + 1, &ret) << 8;
+               scratch |= sdio_readb(card->func, card->scratch_reg + 1,
+                                       &ret) << 8;
 
        if (err)
                *err = ret;
@@ -127,6 +157,46 @@ static u16 if_sdio_read_scratch(struct if_sdio_card *card, int *err)
        return scratch;
 }
 
+static u8 if_sdio_read_rx_unit(struct if_sdio_card *card)
+{
+       int ret;
+       u8 rx_unit;
+
+       rx_unit = sdio_readb(card->func, IF_SDIO_RX_UNIT, &ret);
+
+       if (ret)
+               rx_unit = 0;
+
+       return rx_unit;
+}
+
+static u16 if_sdio_read_rx_len(struct if_sdio_card *card, int *err)
+{
+       int ret;
+       u16 rx_len;
+
+       switch (card->model) {
+       case IF_SDIO_MODEL_8385:
+       case IF_SDIO_MODEL_8686:
+               rx_len = if_sdio_read_scratch(card, &ret);
+               break;
+       case IF_SDIO_MODEL_8688:
+       default: /* for newer chipsets */
+               rx_len = sdio_readb(card->func, IF_SDIO_RX_LEN, &ret);
+               if (!ret)
+                       rx_len <<= card->rx_unit;
+               else
+                       rx_len = 0xffff;        /* invalid length */
+
+               break;
+       }
+
+       if (err)
+               *err = ret;
+
+       return rx_len;
+}
+
 static int if_sdio_handle_cmd(struct if_sdio_card *card,
                u8 *buffer, unsigned size)
 {
@@ -207,7 +277,7 @@ static int if_sdio_handle_event(struct if_sdio_card *card,
 
        lbs_deb_enter(LBS_DEB_SDIO);
 
-       if (card->model == 0x04) {
+       if (card->model == IF_SDIO_MODEL_8385) {
                event = sdio_readb(card->func, IF_SDIO_EVENT, &ret);
                if (ret)
                        goto out;
@@ -245,7 +315,7 @@ static int if_sdio_card_to_host(struct if_sdio_card *card)
 
        lbs_deb_enter(LBS_DEB_SDIO);
 
-       size = if_sdio_read_scratch(card, &ret);
+       size = if_sdio_read_rx_len(card, &ret);
        if (ret)
                goto out;
 
@@ -488,7 +558,6 @@ static int if_sdio_prog_helper(struct if_sdio_card *card)
        ret = 0;
 
 release:
-       sdio_set_block_size(card->func, 0);
        sdio_release_host(card->func);
        kfree(chunk_buffer);
 release_fw:
@@ -624,7 +693,6 @@ static int if_sdio_prog_real(struct if_sdio_card *card)
        ret = 0;
 
 release:
-       sdio_set_block_size(card->func, 0);
        sdio_release_host(card->func);
        kfree(chunk_buffer);
 release_fw:
@@ -653,6 +721,8 @@ static int if_sdio_prog_firmware(struct if_sdio_card *card)
        if (ret)
                goto out;
 
+       lbs_deb_sdio("firmware status = %#x\n", scratch);
+
        if (scratch == IF_SDIO_FIRMWARE_OK) {
                lbs_deb_sdio("firmware already loaded\n");
                goto success;
@@ -667,6 +737,9 @@ static int if_sdio_prog_firmware(struct if_sdio_card *card)
                goto out;
 
 success:
+       sdio_claim_host(card->func);
+       sdio_set_block_size(card->func, IF_SDIO_BLOCK_SIZE);
+       sdio_release_host(card->func);
        ret = 0;
 
 out:
@@ -820,10 +893,10 @@ static int if_sdio_probe(struct sdio_func *func,
                if (sscanf(func->card->info[i],
                                "ID: %x", &model) == 1)
                        break;
-               if (!strcmp(func->card->info[i], "IBIS Wireless SDIO Card")) {
-                       model = 4;
-                       break;
-               }
+               if (!strcmp(func->card->info[i], "IBIS Wireless SDIO Card")) {
+                       model = IF_SDIO_MODEL_8385;
+                       break;
+               }
        }
 
        if (i == func->card->num_info) {
@@ -837,6 +910,20 @@ static int if_sdio_probe(struct sdio_func *func,
 
        card->func = func;
        card->model = model;
+
+       switch (card->model) {
+       case IF_SDIO_MODEL_8385:
+               card->scratch_reg = IF_SDIO_SCRATCH_OLD;
+               break;
+       case IF_SDIO_MODEL_8686:
+               card->scratch_reg = IF_SDIO_SCRATCH;
+               break;
+       case IF_SDIO_MODEL_8688:
+       default: /* for newer chipsets */
+               card->scratch_reg = IF_SDIO_FW_STATUS;
+               break;
+       }
+
        spin_lock_init(&card->lock);
        card->workqueue = create_workqueue("libertas_sdio");
        INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);
@@ -914,22 +1001,44 @@ static int if_sdio_probe(struct sdio_func *func,
 
        priv->fw_ready = 1;
 
+       sdio_claim_host(func);
+
+       /*
+        * Get rx_unit if the chip is SD8688 or newer.
+        * SD8385 & SD8686 do not have rx_unit.
+        */
+       if ((card->model != IF_SDIO_MODEL_8385)
+                       && (card->model != IF_SDIO_MODEL_8686))
+               card->rx_unit = if_sdio_read_rx_unit(card);
+       else
+               card->rx_unit = 0;
+
        /*
         * Enable interrupts now that everything is set up
         */
-       sdio_claim_host(func);
        sdio_writeb(func, 0x0f, IF_SDIO_H_INT_MASK, &ret);
        sdio_release_host(func);
        if (ret)
                goto reclaim;
 
+       /*
+        * FUNC_INIT is required for SD8688 WLAN/BT multiple functions
+        */
+       if (card->model == IF_SDIO_MODEL_8688) {
+               struct cmd_header cmd;
+
+               memset(&cmd, 0, sizeof(cmd));
+
+               lbs_deb_sdio("send function INIT command\n");
+               if (__lbs_cmd(priv, CMD_FUNC_INIT, &cmd, sizeof(cmd),
+                               lbs_cmd_copyback, (unsigned long) &cmd))
+                       lbs_pr_alert("CMD_FUNC_INIT cmd failed\n");
+       }
+
        ret = lbs_start_card(priv);
        if (ret)
                goto err_activate_card;
 
-       if (priv->fwcapinfo & FW_CAPINFO_PS)
-               priv->ps_supported = 1;
-
 out:
        lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
 
@@ -968,11 +1077,27 @@ static void if_sdio_remove(struct sdio_func *func)
 
        card = sdio_get_drvdata(func);
 
-       card->priv->surpriseremoved = 1;
+       if (user_rmmod && (card->model == IF_SDIO_MODEL_8688)) {
+               /*
+                * FUNC_SHUTDOWN is required for SD8688 WLAN/BT
+                * multiple functions
+                */
+               struct cmd_header cmd;
+
+               memset(&cmd, 0, sizeof(cmd));
+
+               lbs_deb_sdio("send function SHUTDOWN command\n");
+               if (__lbs_cmd(card->priv, CMD_FUNC_SHUTDOWN,
+                               &cmd, sizeof(cmd), lbs_cmd_copyback,
+                               (unsigned long) &cmd))
+                       lbs_pr_alert("CMD_FUNC_SHUTDOWN cmd failed\n");
+       }
+
 
        lbs_deb_sdio("call remove card\n");
        lbs_stop_card(card->priv);
        lbs_remove_card(card->priv);
+       card->priv->surpriseremoved = 1;
 
        flush_workqueue(card->workqueue);
        destroy_workqueue(card->workqueue);
@@ -1015,6 +1140,9 @@ static int __init if_sdio_init_module(void)
 
        ret = sdio_register_driver(&if_sdio_driver);
 
+       /* Clear the flag in case user removes the card. */
+       user_rmmod = 0;
+
        lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
 
        return ret;
@@ -1024,6 +1152,9 @@ static void __exit if_sdio_exit_module(void)
 {
        lbs_deb_enter(LBS_DEB_SDIO);
 
+       /* Set the flag as user is removing this module. */
+       user_rmmod = 1;
+
        sdio_unregister_driver(&if_sdio_driver);
 
        lbs_deb_leave(LBS_DEB_SDIO);