]> Pileus Git - ~andy/linux/blobdiff - drivers/net/wireless/ath/ath10k/pci.c
Merge tag 'for-linville-20131203' of git://github.com/kvalo/ath
[~andy/linux] / drivers / net / wireless / ath / ath10k / pci.c
index 327afbcd5692b56b1f836b52cf49604b86facc18..29fd197d1fd8b3b7e8da66b212d0452c4e3ec3de 100644 (file)
 #include "ce.h"
 #include "pci.h"
 
+enum ath10k_pci_irq_mode {
+       ATH10K_PCI_IRQ_AUTO = 0,
+       ATH10K_PCI_IRQ_LEGACY = 1,
+       ATH10K_PCI_IRQ_MSI = 2,
+};
+
 static unsigned int ath10k_target_ps;
+static unsigned int ath10k_pci_irq_mode = ATH10K_PCI_IRQ_AUTO;
+
 module_param(ath10k_target_ps, uint, 0644);
 MODULE_PARM_DESC(ath10k_target_ps, "Enable ath10k Target (SoC) PS option");
 
+module_param_named(irq_mode, ath10k_pci_irq_mode, uint, 0644);
+MODULE_PARM_DESC(irq_mode, "0: auto, 1: legacy, 2: msi (default: 0)");
+
 #define QCA988X_2_0_DEVICE_ID  (0x003c)
 
 static DEFINE_PCI_DEVICE_TABLE(ath10k_pci_id_table) = {
@@ -62,6 +73,7 @@ static void ath10k_pci_free_irq(struct ath10k *ar);
 static int ath10k_pci_bmi_wait(struct ath10k_ce_pipe *tx_pipe,
                               struct ath10k_ce_pipe *rx_pipe,
                               struct bmi_xfer *xfer);
+static void ath10k_pci_cleanup_ce(struct ath10k *ar);
 
 static const struct ce_attr host_ce_config_wlan[] = {
        /* CE0: host->target HTC control and raw streams */
@@ -219,6 +231,74 @@ static bool ath10k_pci_irq_pending(struct ath10k *ar)
        return false;
 }
 
+static void ath10k_pci_disable_and_clear_legacy_irq(struct ath10k *ar)
+{
+       /* IMPORTANT: INTR_CLR register has to be set after
+        * INTR_ENABLE is set to 0, otherwise interrupt can not be
+        * really cleared. */
+       ath10k_pci_write32(ar, SOC_CORE_BASE_ADDRESS + PCIE_INTR_ENABLE_ADDRESS,
+                          0);
+       ath10k_pci_write32(ar, SOC_CORE_BASE_ADDRESS + PCIE_INTR_CLR_ADDRESS,
+                          PCIE_INTR_FIRMWARE_MASK | PCIE_INTR_CE_MASK_ALL);
+
+       /* IMPORTANT: this extra read transaction is required to
+        * flush the posted write buffer. */
+       (void) ath10k_pci_read32(ar, SOC_CORE_BASE_ADDRESS +
+                                PCIE_INTR_ENABLE_ADDRESS);
+}
+
+static void ath10k_pci_enable_legacy_irq(struct ath10k *ar)
+{
+       ath10k_pci_write32(ar, SOC_CORE_BASE_ADDRESS +
+                          PCIE_INTR_ENABLE_ADDRESS,
+                          PCIE_INTR_FIRMWARE_MASK | PCIE_INTR_CE_MASK_ALL);
+
+       /* IMPORTANT: this extra read transaction is required to
+        * flush the posted write buffer. */
+       (void) ath10k_pci_read32(ar, SOC_CORE_BASE_ADDRESS +
+                                PCIE_INTR_ENABLE_ADDRESS);
+}
+
+static irqreturn_t ath10k_pci_early_irq_handler(int irq, void *arg)
+{
+       struct ath10k *ar = arg;
+       struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+
+       if (ar_pci->num_msi_intrs == 0) {
+               if (!ath10k_pci_irq_pending(ar))
+                       return IRQ_NONE;
+
+               ath10k_pci_disable_and_clear_legacy_irq(ar);
+       }
+
+       tasklet_schedule(&ar_pci->early_irq_tasklet);
+
+       return IRQ_HANDLED;
+}
+
+static int ath10k_pci_request_early_irq(struct ath10k *ar)
+{
+       struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+       int ret;
+
+       /* Regardless whether MSI-X/MSI/legacy irqs have been set up the first
+        * interrupt from irq vector is triggered in all cases for FW
+        * indication/errors */
+       ret = request_irq(ar_pci->pdev->irq, ath10k_pci_early_irq_handler,
+                         IRQF_SHARED, "ath10k_pci (early)", ar);
+       if (ret) {
+               ath10k_warn("failed to request early irq: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static void ath10k_pci_free_early_irq(struct ath10k *ar)
+{
+       free_irq(ath10k_pci_priv(ar)->pdev->irq, ar);
+}
+
 /*
  * Diagnostic read/write access is provided for startup/config/debug usage.
  * Caller must guarantee proper alignment, when applicable, and single user
@@ -824,14 +904,13 @@ static void ath10k_pci_hif_set_callbacks(struct ath10k *ar,
               sizeof(ar_pci->msg_callbacks_current));
 }
 
-static int ath10k_pci_start_ce(struct ath10k *ar)
+static int ath10k_pci_alloc_compl(struct ath10k *ar)
 {
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
-       struct ath10k_ce_pipe *ce_diag = ar_pci->ce_diag;
        const struct ce_attr *attr;
        struct ath10k_pci_pipe *pipe_info;
        struct ath10k_pci_compl *compl;
-       int i, pipe_num, completions, disable_interrupts;
+       int i, pipe_num, completions;
 
        spin_lock_init(&ar_pci->compl_lock);
        INIT_LIST_HEAD(&ar_pci->compl_process);
@@ -843,34 +922,23 @@ static int ath10k_pci_start_ce(struct ath10k *ar)
                INIT_LIST_HEAD(&pipe_info->compl_free);
 
                /* Handle Diagnostic CE specially */
-               if (pipe_info->ce_hdl == ce_diag)
+               if (pipe_info->ce_hdl == ar_pci->ce_diag)
                        continue;
 
                attr = &host_ce_config_wlan[pipe_num];
                completions = 0;
 
-               if (attr->src_nentries) {
-                       disable_interrupts = attr->flags & CE_ATTR_DIS_INTR;
-                       ath10k_ce_send_cb_register(pipe_info->ce_hdl,
-                                                  ath10k_pci_ce_send_done,
-                                                  disable_interrupts);
+               if (attr->src_nentries)
                        completions += attr->src_nentries;
-               }
 
-               if (attr->dest_nentries) {
-                       ath10k_ce_recv_cb_register(pipe_info->ce_hdl,
-                                                  ath10k_pci_ce_recv_data);
+               if (attr->dest_nentries)
                        completions += attr->dest_nentries;
-               }
-
-               if (completions == 0)
-                       continue;
 
                for (i = 0; i < completions; i++) {
                        compl = kmalloc(sizeof(*compl), GFP_KERNEL);
                        if (!compl) {
                                ath10k_warn("No memory for completion state\n");
-                               ath10k_pci_stop_ce(ar);
+                               ath10k_pci_cleanup_ce(ar);
                                return -ENOMEM;
                        }
 
@@ -882,6 +950,37 @@ static int ath10k_pci_start_ce(struct ath10k *ar)
        return 0;
 }
 
+static int ath10k_pci_setup_ce_irq(struct ath10k *ar)
+{
+       struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+       const struct ce_attr *attr;
+       struct ath10k_pci_pipe *pipe_info;
+       int pipe_num, disable_interrupts;
+
+       for (pipe_num = 0; pipe_num < CE_COUNT; pipe_num++) {
+               pipe_info = &ar_pci->pipe_info[pipe_num];
+
+               /* Handle Diagnostic CE specially */
+               if (pipe_info->ce_hdl == ar_pci->ce_diag)
+                       continue;
+
+               attr = &host_ce_config_wlan[pipe_num];
+
+               if (attr->src_nentries) {
+                       disable_interrupts = attr->flags & CE_ATTR_DIS_INTR;
+                       ath10k_ce_send_cb_register(pipe_info->ce_hdl,
+                                                  ath10k_pci_ce_send_done,
+                                                  disable_interrupts);
+               }
+
+               if (attr->dest_nentries)
+                       ath10k_ce_recv_cb_register(pipe_info->ce_hdl,
+                                                  ath10k_pci_ce_recv_data);
+       }
+
+       return 0;
+}
+
 static void ath10k_pci_kill_tasklet(struct ath10k *ar)
 {
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
@@ -889,6 +988,7 @@ static void ath10k_pci_kill_tasklet(struct ath10k *ar)
 
        tasklet_kill(&ar_pci->intr_tq);
        tasklet_kill(&ar_pci->msi_fw_err);
+       tasklet_kill(&ar_pci->early_irq_tasklet);
 
        for (i = 0; i < CE_COUNT; i++)
                tasklet_kill(&ar_pci->pipe_info[i].intr);
@@ -899,13 +999,6 @@ static void ath10k_pci_stop_ce(struct ath10k *ar)
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
        struct ath10k_pci_compl *compl;
        struct sk_buff *skb;
-       int ret;
-
-       ret = ath10k_ce_disable_interrupts(ar);
-       if (ret)
-               ath10k_warn("failed to disable CE interrupts: %d\n", ret);
-
-       ath10k_pci_kill_tasklet(ar);
 
        /* Mark pending completions as aborted, so that upper layers free up
         * their associated resources */
@@ -1208,12 +1301,28 @@ static int ath10k_pci_post_rx(struct ath10k *ar)
 static int ath10k_pci_hif_start(struct ath10k *ar)
 {
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
-       int ret;
+       int ret, ret_early;
 
-       ret = ath10k_pci_start_ce(ar);
+       ath10k_pci_free_early_irq(ar);
+       ath10k_pci_kill_tasklet(ar);
+
+       ret = ath10k_pci_alloc_compl(ar);
        if (ret) {
-               ath10k_warn("failed to start CE: %d\n", ret);
-               return ret;
+               ath10k_warn("failed to allocate CE completions: %d\n", ret);
+               goto err_early_irq;
+       }
+
+       ret = ath10k_pci_request_irq(ar);
+       if (ret) {
+               ath10k_warn("failed to post RX buffers for all pipes: %d\n",
+                           ret);
+               goto err_free_compl;
+       }
+
+       ret = ath10k_pci_setup_ce_irq(ar);
+       if (ret) {
+               ath10k_warn("failed to setup CE interrupts: %d\n", ret);
+               goto err_stop;
        }
 
        /* Post buffers once to start things off. */
@@ -1221,11 +1330,29 @@ static int ath10k_pci_hif_start(struct ath10k *ar)
        if (ret) {
                ath10k_warn("failed to post RX buffers for all pipes: %d\n",
                            ret);
-               return ret;
+               goto err_stop;
        }
 
        ar_pci->started = 1;
        return 0;
+
+err_stop:
+       ath10k_ce_disable_interrupts(ar);
+       ath10k_pci_free_irq(ar);
+       ath10k_pci_kill_tasklet(ar);
+       ath10k_pci_stop_ce(ar);
+       ath10k_pci_process_ce(ar);
+err_free_compl:
+       ath10k_pci_cleanup_ce(ar);
+err_early_irq:
+       /* Though there should be no interrupts (device was reset)
+        * power_down() expects the early IRQ to be installed as per the
+        * driver lifecycle. */
+       ret_early = ath10k_pci_request_early_irq(ar);
+       if (ret_early)
+               ath10k_warn("failed to re-enable early irq: %d\n", ret_early);
+
+       return ret;
 }
 
 static void ath10k_pci_rx_pipe_cleanup(struct ath10k_pci_pipe *pipe_info)
@@ -1343,27 +1470,25 @@ static void ath10k_pci_ce_deinit(struct ath10k *ar)
        }
 }
 
-static void ath10k_pci_disable_irqs(struct ath10k *ar)
-{
-       struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
-       int i;
-
-       for (i = 0; i < max(1, ar_pci->num_msi_intrs); i++)
-               disable_irq(ar_pci->pdev->irq + i);
-}
-
 static void ath10k_pci_hif_stop(struct ath10k *ar)
 {
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+       int ret;
 
        ath10k_dbg(ATH10K_DBG_PCI, "%s\n", __func__);
 
-       /* Irqs are never explicitly re-enabled. They are implicitly re-enabled
-        * by upon power_up. */
-       ath10k_pci_disable_irqs(ar);
+       ret = ath10k_ce_disable_interrupts(ar);
+       if (ret)
+               ath10k_warn("failed to disable CE interrupts: %d\n", ret);
 
+       ath10k_pci_free_irq(ar);
+       ath10k_pci_kill_tasklet(ar);
        ath10k_pci_stop_ce(ar);
 
+       ret = ath10k_pci_request_early_irq(ar);
+       if (ret)
+               ath10k_warn("failed to re-enable early irq: %d\n", ret);
+
        /* At this point, asynchronous threads are stopped, the target should
         * not DMA nor interrupt. We process the leftovers and then free
         * everything else up. */
@@ -1912,34 +2037,28 @@ static int ath10k_pci_hif_power_up(struct ath10k *ar)
                goto err_ce;
        }
 
-       ret = ath10k_pci_request_irq(ar);
+       ret = ath10k_pci_request_early_irq(ar);
        if (ret) {
-               ath10k_err("failed to request irqs: %d\n", ret);
+               ath10k_err("failed to request early irq: %d\n", ret);
                goto err_deinit_irq;
        }
 
        ret = ath10k_pci_wait_for_target_init(ar);
        if (ret) {
                ath10k_err("failed to wait for target to init: %d\n", ret);
-               goto err_free_irq;
-       }
-
-       ret = ath10k_ce_enable_err_irq(ar);
-       if (ret) {
-               ath10k_err("failed to enable CE error irq: %d\n", ret);
-               goto err_free_irq;
+               goto err_free_early_irq;
        }
 
        ret = ath10k_pci_init_config(ar);
        if (ret) {
                ath10k_err("failed to setup init config: %d\n", ret);
-               goto err_free_irq;
+               goto err_free_early_irq;
        }
 
        ret = ath10k_pci_wake_target_cpu(ar);
        if (ret) {
                ath10k_err("could not wake up target CPU: %d\n", ret);
-               goto err_free_irq;
+               goto err_free_early_irq;
        }
 
        if (ar_pci->num_msi_intrs > 1)
@@ -1954,14 +2073,13 @@ static int ath10k_pci_hif_power_up(struct ath10k *ar)
 
        return 0;
 
-err_free_irq:
-       ath10k_pci_free_irq(ar);
-       ath10k_pci_kill_tasklet(ar);
-       ath10k_pci_device_reset(ar);
+err_free_early_irq:
+       ath10k_pci_free_early_irq(ar);
 err_deinit_irq:
        ath10k_pci_deinit_irq(ar);
 err_ce:
        ath10k_pci_ce_deinit(ar);
+       ath10k_pci_device_reset(ar);
 err_ps:
        if (!test_bit(ATH10K_PCI_FEATURE_SOC_POWER_SAVE, ar_pci->features))
                ath10k_do_pci_sleep(ar);
@@ -1973,7 +2091,8 @@ static void ath10k_pci_hif_power_down(struct ath10k *ar)
 {
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
 
-       ath10k_pci_free_irq(ar);
+       ath10k_pci_free_early_irq(ar);
+       ath10k_pci_kill_tasklet(ar);
        ath10k_pci_deinit_irq(ar);
        ath10k_pci_device_reset(ar);
 
@@ -2114,25 +2233,7 @@ static irqreturn_t ath10k_pci_interrupt_handler(int irq, void *arg)
                if (!ath10k_pci_irq_pending(ar))
                        return IRQ_NONE;
 
-               /*
-                * IMPORTANT: INTR_CLR regiser has to be set after
-                * INTR_ENABLE is set to 0, otherwise interrupt can not be
-                * really cleared.
-                */
-               iowrite32(0, ar_pci->mem +
-                         (SOC_CORE_BASE_ADDRESS |
-                          PCIE_INTR_ENABLE_ADDRESS));
-               iowrite32(PCIE_INTR_FIRMWARE_MASK |
-                         PCIE_INTR_CE_MASK_ALL,
-                         ar_pci->mem + (SOC_CORE_BASE_ADDRESS |
-                                        PCIE_INTR_CLR_ADDRESS));
-               /*
-                * IMPORTANT: this extra read transaction is required to
-                * flush the posted write buffer.
-                */
-               (void) ioread32(ar_pci->mem +
-                               (SOC_CORE_BASE_ADDRESS |
-                                PCIE_INTR_ENABLE_ADDRESS));
+               ath10k_pci_disable_and_clear_legacy_irq(ar);
        }
 
        tasklet_schedule(&ar_pci->intr_tq);
@@ -2140,6 +2241,34 @@ static irqreturn_t ath10k_pci_interrupt_handler(int irq, void *arg)
        return IRQ_HANDLED;
 }
 
+static void ath10k_pci_early_irq_tasklet(unsigned long data)
+{
+       struct ath10k *ar = (struct ath10k *)data;
+       struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+       u32 fw_ind;
+       int ret;
+
+       ret = ath10k_pci_wake(ar);
+       if (ret) {
+               ath10k_warn("failed to wake target in early irq tasklet: %d\n",
+                           ret);
+               return;
+       }
+
+       fw_ind = ath10k_pci_read32(ar, ar_pci->fw_indicator_address);
+       if (fw_ind & FW_IND_EVENT_PENDING) {
+               ath10k_pci_write32(ar, ar_pci->fw_indicator_address,
+                                  fw_ind & ~FW_IND_EVENT_PENDING);
+
+               /* Some structures are unavailable during early boot or at
+                * driver teardown so just print that the device has crashed. */
+               ath10k_warn("device crashed - no diagnostics available\n");
+       }
+
+       ath10k_pci_sleep(ar);
+       ath10k_pci_enable_legacy_irq(ar);
+}
+
 static void ath10k_pci_tasklet(unsigned long data)
 {
        struct ath10k *ar = (struct ath10k *)data;
@@ -2148,20 +2277,9 @@ static void ath10k_pci_tasklet(unsigned long data)
        ath10k_pci_fw_interrupt_handler(ar); /* FIXME: Handle FW error */
        ath10k_ce_per_engine_service_any(ar);
 
-       if (ar_pci->num_msi_intrs == 0) {
-               /* Enable Legacy PCI line interrupts */
-               iowrite32(PCIE_INTR_FIRMWARE_MASK |
-                         PCIE_INTR_CE_MASK_ALL,
-                         ar_pci->mem + (SOC_CORE_BASE_ADDRESS |
-                                        PCIE_INTR_ENABLE_ADDRESS));
-               /*
-                * IMPORTANT: this extra read transaction is required to
-                * flush the posted write buffer
-                */
-               (void) ioread32(ar_pci->mem +
-                               (SOC_CORE_BASE_ADDRESS |
-                                PCIE_INTR_ENABLE_ADDRESS));
-       }
+       /* Re-enable legacy irq that was disabled in the irq handler */
+       if (ar_pci->num_msi_intrs == 0)
+               ath10k_pci_enable_legacy_irq(ar);
 }
 
 static int ath10k_pci_request_irq_msix(struct ath10k *ar)
@@ -2267,6 +2385,8 @@ static void ath10k_pci_init_irq_tasklets(struct ath10k *ar)
        tasklet_init(&ar_pci->intr_tq, ath10k_pci_tasklet, (unsigned long)ar);
        tasklet_init(&ar_pci->msi_fw_err, ath10k_msi_err_tasklet,
                     (unsigned long)ar);
+       tasklet_init(&ar_pci->early_irq_tasklet, ath10k_pci_early_irq_tasklet,
+                    (unsigned long)ar);
 
        for (i = 0; i < CE_COUNT; i++) {
                ar_pci->pipe_info[i].ar_pci = ar_pci;
@@ -2278,27 +2398,37 @@ static void ath10k_pci_init_irq_tasklets(struct ath10k *ar)
 static int ath10k_pci_init_irq(struct ath10k *ar)
 {
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+       bool msix_supported = test_bit(ATH10K_PCI_FEATURE_MSI_X,
+                                      ar_pci->features);
        int ret;
 
        ath10k_pci_init_irq_tasklets(ar);
 
-       if (!test_bit(ATH10K_PCI_FEATURE_MSI_X, ar_pci->features))
-               goto msi;
+       if (ath10k_pci_irq_mode != ATH10K_PCI_IRQ_AUTO &&
+           !test_bit(ATH10K_FLAG_FIRST_BOOT_DONE, &ar->dev_flags))
+               ath10k_info("limiting irq mode to: %d\n", ath10k_pci_irq_mode);
 
        /* Try MSI-X */
-       ar_pci->num_msi_intrs = MSI_NUM_REQUEST;
-       ret = pci_enable_msi_block(ar_pci->pdev, ar_pci->num_msi_intrs);
-       if (ret == 0)
-               return 0;
-       if (ret > 0)
-               pci_disable_msi(ar_pci->pdev);
+       if (ath10k_pci_irq_mode == ATH10K_PCI_IRQ_AUTO && msix_supported) {
+               ar_pci->num_msi_intrs = MSI_NUM_REQUEST;
+               ret = pci_enable_msi_block(ar_pci->pdev, ar_pci->num_msi_intrs);
+               if (ret == 0)
+                       return 0;
+               if (ret > 0)
+                       pci_disable_msi(ar_pci->pdev);
+
+               /* fall-through */
+       }
 
-msi:
        /* Try MSI */
-       ar_pci->num_msi_intrs = 1;
-       ret = pci_enable_msi(ar_pci->pdev);
-       if (ret == 0)
-               return 0;
+       if (ath10k_pci_irq_mode != ATH10K_PCI_IRQ_LEGACY) {
+               ar_pci->num_msi_intrs = 1;
+               ret = pci_enable_msi(ar_pci->pdev);
+               if (ret == 0)
+                       return 0;
+
+               /* fall-through */
+       }
 
        /* Try legacy irq
         *
@@ -2475,7 +2605,7 @@ static int ath10k_pci_probe(struct pci_dev *pdev,
                break;
        default:
                ret = -ENODEV;
-               ath10k_err("Unkown device ID: %d\n", pci_dev->device);
+               ath10k_err("Unknown device ID: %d\n", pci_dev->device);
                goto err_ar_pci;
        }