dmaengine: qcom_hidma: add MSI support for interrupts
authorSinan Kaya <okaya@codeaurora.org>
Fri, 21 Oct 2016 16:37:59 +0000 (12:37 -0400)
committerVinod Koul <vinod.koul@intel.com>
Thu, 3 Nov 2016 13:25:45 +0000 (18:55 +0530)
The interrupts can now be delivered as platform MSI interrupts on newer
platforms. The code looks for a new OF and ACPI strings in order to enable
the functionality.

Signed-off-by: Sinan Kaya <okaya@codeaurora.org>
Signed-off-by: Vinod Koul <vinod.koul@intel.com>
drivers/dma/qcom/hidma.c
drivers/dma/qcom/hidma.h
drivers/dma/qcom/hidma_ll.c

index e244e10..d5e7991 100644 (file)
@@ -56,6 +56,7 @@
 #include <linux/irq.h>
 #include <linux/atomic.h>
 #include <linux/pm_runtime.h>
+#include <linux/msi.h>
 
 #include "../dmaengine.h"
 #include "hidma.h"
@@ -70,6 +71,7 @@
 #define HIDMA_ERR_INFO_SW                      0xFF
 #define HIDMA_ERR_CODE_UNEXPECTED_TERMINATE    0x0
 #define HIDMA_NR_DEFAULT_DESC                  10
+#define HIDMA_MSI_INTS                         11
 
 static inline struct hidma_dev *to_hidma_dev(struct dma_device *dmadev)
 {
@@ -553,6 +555,15 @@ static irqreturn_t hidma_chirq_handler(int chirq, void *arg)
        return hidma_ll_inthandler(chirq, lldev);
 }
 
+static irqreturn_t hidma_chirq_handler_msi(int chirq, void *arg)
+{
+       struct hidma_lldev **lldevp = arg;
+       struct hidma_dev *dmadev = to_hidma_dev_from_lldev(lldevp);
+
+       return hidma_ll_inthandler_msi(chirq, *lldevp,
+                                      1 << (chirq - dmadev->msi_virqbase));
+}
+
 static ssize_t hidma_show_values(struct device *dev,
                                 struct device_attribute *attr, char *buf)
 {
@@ -590,6 +601,104 @@ static int hidma_create_sysfs_entry(struct hidma_dev *dev, char *name,
        return device_create_file(dev->ddev.dev, attrs);
 }
 
+#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
+static void hidma_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
+{
+       struct device *dev = msi_desc_to_dev(desc);
+       struct hidma_dev *dmadev = dev_get_drvdata(dev);
+
+       if (!desc->platform.msi_index) {
+               writel(msg->address_lo, dmadev->dev_evca + 0x118);
+               writel(msg->address_hi, dmadev->dev_evca + 0x11C);
+               writel(msg->data, dmadev->dev_evca + 0x120);
+       }
+}
+#endif
+
+static void hidma_free_msis(struct hidma_dev *dmadev)
+{
+#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
+       struct device *dev = dmadev->ddev.dev;
+       struct msi_desc *desc;
+
+       /* free allocated MSI interrupts above */
+       for_each_msi_entry(desc, dev)
+               devm_free_irq(dev, desc->irq, &dmadev->lldev);
+
+       platform_msi_domain_free_irqs(dev);
+#endif
+}
+
+static int hidma_request_msi(struct hidma_dev *dmadev,
+                            struct platform_device *pdev)
+{
+#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
+       int rc;
+       struct msi_desc *desc;
+       struct msi_desc *failed_desc = NULL;
+
+       rc = platform_msi_domain_alloc_irqs(&pdev->dev, HIDMA_MSI_INTS,
+                                           hidma_write_msi_msg);
+       if (rc)
+               return rc;
+
+       for_each_msi_entry(desc, &pdev->dev) {
+               if (!desc->platform.msi_index)
+                       dmadev->msi_virqbase = desc->irq;
+
+               rc = devm_request_irq(&pdev->dev, desc->irq,
+                                      hidma_chirq_handler_msi,
+                                      0, "qcom-hidma-msi",
+                                      &dmadev->lldev);
+               if (rc) {
+                       failed_desc = desc;
+                       break;
+               }
+       }
+
+       if (rc) {
+               /* free allocated MSI interrupts above */
+               for_each_msi_entry(desc, &pdev->dev) {
+                       if (desc == failed_desc)
+                               break;
+                       devm_free_irq(&pdev->dev, desc->irq,
+                                     &dmadev->lldev);
+               }
+       } else {
+               /* Add callback to free MSIs on teardown */
+               hidma_ll_setup_irq(dmadev->lldev, true);
+
+       }
+       if (rc)
+               dev_warn(&pdev->dev,
+                        "failed to request MSI irq, falling back to wired IRQ\n");
+       return rc;
+#else
+       return -EINVAL;
+#endif
+}
+
+static bool hidma_msi_capable(struct device *dev)
+{
+       struct acpi_device *adev = ACPI_COMPANION(dev);
+       const char *of_compat;
+       int ret = -EINVAL;
+
+       if (!adev || acpi_disabled) {
+               ret = device_property_read_string(dev, "compatible",
+                                                 &of_compat);
+               if (ret)
+                       return false;
+
+               ret = strcmp(of_compat, "qcom,hidma-1.1");
+       } else {
+#ifdef CONFIG_ACPI
+               ret = strcmp(acpi_device_hid(adev), "QCOM8062");
+#endif
+       }
+       return ret == 0;
+}
+
 static int hidma_probe(struct platform_device *pdev)
 {
        struct hidma_dev *dmadev;
@@ -599,6 +708,7 @@ static int hidma_probe(struct platform_device *pdev)
        void __iomem *evca;
        void __iomem *trca;
        int rc;
+       bool msi;
 
        pm_runtime_set_autosuspend_delay(&pdev->dev, HIDMA_AUTOSUSPEND_TIMEOUT);
        pm_runtime_use_autosuspend(&pdev->dev);
@@ -660,6 +770,12 @@ static int hidma_probe(struct platform_device *pdev)
        dmadev->ddev.device_terminate_all = hidma_terminate_all;
        dmadev->ddev.copy_align = 8;
 
+       /*
+        * Determine the MSI capability of the platform. Old HW doesn't
+        * support MSI.
+        */
+       msi = hidma_msi_capable(&pdev->dev);
+
        device_property_read_u32(&pdev->dev, "desc-count",
                                 &dmadev->nr_descriptors);
 
@@ -688,10 +804,17 @@ static int hidma_probe(struct platform_device *pdev)
                goto dmafree;
        }
 
-       rc = devm_request_irq(&pdev->dev, chirq, hidma_chirq_handler, 0,
-                             "qcom-hidma", dmadev->lldev);
-       if (rc)
-               goto uninit;
+       platform_set_drvdata(pdev, dmadev);
+       if (msi)
+               rc = hidma_request_msi(dmadev, pdev);
+
+       if (!msi || rc) {
+               hidma_ll_setup_irq(dmadev->lldev, false);
+               rc = devm_request_irq(&pdev->dev, chirq, hidma_chirq_handler,
+                                     0, "qcom-hidma", dmadev->lldev);
+               if (rc)
+                       goto uninit;
+       }
 
        INIT_LIST_HEAD(&dmadev->ddev.channels);
        rc = hidma_chan_init(dmadev, 0);
@@ -707,12 +830,14 @@ static int hidma_probe(struct platform_device *pdev)
        hidma_debug_init(dmadev);
        hidma_create_sysfs_entry(dmadev, "chid", S_IRUGO);
        dev_info(&pdev->dev, "HI-DMA engine driver registration complete\n");
-       platform_set_drvdata(pdev, dmadev);
        pm_runtime_mark_last_busy(dmadev->ddev.dev);
        pm_runtime_put_autosuspend(dmadev->ddev.dev);
        return 0;
 
 uninit:
+       if (msi)
+               hidma_free_msis(dmadev);
+
        hidma_debug_uninit(dmadev);
        hidma_ll_uninit(dmadev->lldev);
 dmafree:
@@ -730,7 +855,11 @@ static int hidma_remove(struct platform_device *pdev)
 
        pm_runtime_get_sync(dmadev->ddev.dev);
        dma_async_device_unregister(&dmadev->ddev);
-       devm_free_irq(dmadev->ddev.dev, dmadev->irq, dmadev->lldev);
+       if (!dmadev->lldev->msi_support)
+               devm_free_irq(dmadev->ddev.dev, dmadev->irq, dmadev->lldev);
+       else
+               hidma_free_msis(dmadev);
+
        tasklet_kill(&dmadev->task);
        hidma_debug_uninit(dmadev);
        hidma_ll_uninit(dmadev->lldev);
@@ -746,12 +875,14 @@ static int hidma_remove(struct platform_device *pdev)
 #if IS_ENABLED(CONFIG_ACPI)
 static const struct acpi_device_id hidma_acpi_ids[] = {
        {"QCOM8061"},
+       {"QCOM8062"},
        {},
 };
 #endif
 
 static const struct of_device_id hidma_match[] = {
        {.compatible = "qcom,hidma-1.0",},
+       {.compatible = "qcom,hidma-1.1",},
        {},
 };
 MODULE_DEVICE_TABLE(of, hidma_match);
index 181f7e0..05f8ba4 100644 (file)
@@ -115,6 +115,7 @@ struct hidma_dev {
        int                             irq;
        int                             chidx;
        u32                             nr_descriptors;
+       int                             msi_virqbase;
 
        struct hidma_lldev              *lldev;
        void                            __iomem *dev_trca;
@@ -153,6 +154,7 @@ struct hidma_lldev *hidma_ll_init(struct device *dev, u32 max_channels,
                        u8 chidx);
 int hidma_ll_uninit(struct hidma_lldev *llhndl);
 irqreturn_t hidma_ll_inthandler(int irq, void *arg);
+irqreturn_t hidma_ll_inthandler_msi(int irq, void *arg, int cause);
 void hidma_cleanup_pending_tre(struct hidma_lldev *llhndl, u8 err_info,
                                u8 err_code);
 int hidma_debug_init(struct hidma_dev *dmadev);
index 7fe43af..6645bdf 100644 (file)
@@ -457,6 +457,14 @@ irqreturn_t hidma_ll_inthandler(int chirq, void *arg)
        return IRQ_HANDLED;
 }
 
+irqreturn_t hidma_ll_inthandler_msi(int chirq, void *arg, int cause)
+{
+       struct hidma_lldev *lldev = arg;
+
+       hidma_ll_int_handler_internal(lldev, cause);
+       return IRQ_HANDLED;
+}
+
 int hidma_ll_enable(struct hidma_lldev *lldev)
 {
        u32 val;