Merge branches 'iommu/fixes', 'x86/amd', 'groups', 'arm/tegra' and 'api/domain-attr...
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / drivers / iommu / tegra-smmu.c
index 3f3d09d560ea3c6ce5e3bc7d019e377ac9e63dfe..4ba325ab626249c2aa2aee1271a6d0c68ec20d9a 100644 (file)
 #include <linux/sched.h>
 #include <linux/iommu.h>
 #include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_iommu.h>
 
 #include <asm/page.h>
 #include <asm/cacheflush.h>
 
 #include <mach/iomap.h>
 #include <mach/smmu.h>
+#include <mach/tegra-ahb.h>
 
 /* bitmap of the page sizes currently supported */
 #define SMMU_IOMMU_PGSIZES     (SZ_4K)
 
 #define SMMU_PDE_NEXT_SHIFT            28
 
-/* AHB Arbiter Registers */
-#define AHB_XBAR_CTRL                          0xe0
-#define AHB_XBAR_CTRL_SMMU_INIT_DONE_DONE      1
-#define AHB_XBAR_CTRL_SMMU_INIT_DONE_SHIFT     17
-
-#define SMMU_NUM_ASIDS                         4
 #define SMMU_TLB_FLUSH_VA_SECTION__MASK                0xffc00000
 #define SMMU_TLB_FLUSH_VA_SECTION__SHIFT       12 /* right shift */
 #define SMMU_TLB_FLUSH_VA_GROUP__MASK          0xffffc000
 
 #define SMMU_PAGE_SHIFT 12
 #define SMMU_PAGE_SIZE (1 << SMMU_PAGE_SHIFT)
+#define SMMU_PAGE_MASK ((1 << SMMU_PAGE_SHIFT) - 1)
 
 #define SMMU_PDIR_COUNT        1024
 #define SMMU_PDIR_SIZE (sizeof(unsigned long) * SMMU_PDIR_COUNT)
 #define SMMU_ASID_DISABLE      0
 #define SMMU_ASID_ASID(n)      ((n) & ~SMMU_ASID_ENABLE(0))
 
+#define NUM_SMMU_REG_BANKS     3
+
 #define smmu_client_enable_hwgrp(c, m) smmu_client_set_hwgrp(c, m, 1)
 #define smmu_client_disable_hwgrp(c)   smmu_client_set_hwgrp(c, 0, 0)
 #define __smmu_client_enable_hwgrp(c, m) __smmu_client_set_hwgrp(c, m, 1)
@@ -235,14 +235,12 @@ struct smmu_as {
  * Per SMMU device - IOMMU device
  */
 struct smmu_device {
-       void __iomem    *regs, *regs_ahbarb;
+       void __iomem    *regs[NUM_SMMU_REG_BANKS];
        unsigned long   iovmm_base;     /* remappable base address */
        unsigned long   page_count;     /* total remappable size */
        spinlock_t      lock;
        char            *name;
        struct device   *dev;
-       int             num_as;
-       struct smmu_as  *as;            /* Run-time allocated array */
        struct page *avp_vector_page;   /* dummy page shared by all AS's */
 
        /*
@@ -252,29 +250,50 @@ struct smmu_device {
        unsigned long translation_enable_1;
        unsigned long translation_enable_2;
        unsigned long asid_security;
+
+       struct device_node *ahb;
+
+       int             num_as;
+       struct smmu_as  as[0];          /* Run-time allocated array */
 };
 
 static struct smmu_device *smmu_handle; /* unique for a system */
 
 /*
- *     SMMU/AHB register accessors
+ *     SMMU register accessors
  */
 static inline u32 smmu_read(struct smmu_device *smmu, size_t offs)
 {
-       return readl(smmu->regs + offs);
-}
-static inline void smmu_write(struct smmu_device *smmu, u32 val, size_t offs)
-{
-       writel(val, smmu->regs + offs);
+       BUG_ON(offs < 0x10);
+       if (offs < 0x3c)
+               return readl(smmu->regs[0] + offs - 0x10);
+       BUG_ON(offs < 0x1f0);
+       if (offs < 0x200)
+               return readl(smmu->regs[1] + offs - 0x1f0);
+       BUG_ON(offs < 0x228);
+       if (offs < 0x284)
+               return readl(smmu->regs[2] + offs - 0x228);
+       BUG();
 }
 
-static inline u32 ahb_read(struct smmu_device *smmu, size_t offs)
-{
-       return readl(smmu->regs_ahbarb + offs);
-}
-static inline void ahb_write(struct smmu_device *smmu, u32 val, size_t offs)
+static inline void smmu_write(struct smmu_device *smmu, u32 val, size_t offs)
 {
-       writel(val, smmu->regs_ahbarb + offs);
+       BUG_ON(offs < 0x10);
+       if (offs < 0x3c) {
+               writel(val, smmu->regs[0] + offs - 0x10);
+               return;
+       }
+       BUG_ON(offs < 0x1f0);
+       if (offs < 0x200) {
+               writel(val, smmu->regs[1] + offs - 0x1f0);
+               return;
+       }
+       BUG_ON(offs < 0x228);
+       if (offs < 0x284) {
+               writel(val, smmu->regs[2] + offs - 0x228);
+               return;
+       }
+       BUG();
 }
 
 #define VA_PAGE_TO_PA(va, page)        \
@@ -370,7 +389,7 @@ static void smmu_flush_regs(struct smmu_device *smmu, int enable)
        FLUSH_SMMU_REGS(smmu);
 }
 
-static void smmu_setup_regs(struct smmu_device *smmu)
+static int smmu_setup_regs(struct smmu_device *smmu)
 {
        int i;
        u32 val;
@@ -398,10 +417,7 @@ static void smmu_setup_regs(struct smmu_device *smmu)
 
        smmu_flush_regs(smmu, 1);
 
-       val = ahb_read(smmu, AHB_XBAR_CTRL);
-       val |= AHB_XBAR_CTRL_SMMU_INIT_DONE_DONE <<
-               AHB_XBAR_CTRL_SMMU_INIT_DONE_SHIFT;
-       ahb_write(smmu, val, AHB_XBAR_CTRL);
+       return tegra_ahb_enable_smmu(smmu->ahb);
 }
 
 static void flush_ptc_and_tlb(struct smmu_device *smmu,
@@ -537,33 +553,42 @@ static inline void put_signature(struct smmu_as *as,
 #endif
 
 /*
- * Caller must lock/unlock as
+ * Caller must not hold as->lock
  */
 static int alloc_pdir(struct smmu_as *as)
 {
-       unsigned long *pdir;
-       int pdn;
+       unsigned long *pdir, flags;
+       int pdn, err = 0;
        u32 val;
        struct smmu_device *smmu = as->smmu;
+       struct page *page;
+       unsigned int *cnt;
+
+       /*
+        * do the allocation, then grab as->lock
+        */
+       cnt = devm_kzalloc(smmu->dev,
+                          sizeof(cnt[0]) * SMMU_PDIR_COUNT,
+                          GFP_KERNEL);
+       page = alloc_page(GFP_KERNEL | __GFP_DMA);
 
-       if (as->pdir_page)
-               return 0;
+       spin_lock_irqsave(&as->lock, flags);
 
-       as->pte_count = devm_kzalloc(smmu->dev,
-                    sizeof(as->pte_count[0]) * SMMU_PDIR_COUNT, GFP_ATOMIC);
-       if (!as->pte_count) {
-               dev_err(smmu->dev,
-                       "failed to allocate smmu_device PTE cunters\n");
-               return -ENOMEM;
+       if (as->pdir_page) {
+               /* We raced, free the redundant */
+               err = -EAGAIN;
+               goto err_out;
        }
-       as->pdir_page = alloc_page(GFP_ATOMIC | __GFP_DMA);
-       if (!as->pdir_page) {
-               dev_err(smmu->dev,
-                       "failed to allocate smmu_device page directory\n");
-               devm_kfree(smmu->dev, as->pte_count);
-               as->pte_count = NULL;
-               return -ENOMEM;
+
+       if (!page || !cnt) {
+               dev_err(smmu->dev, "failed to allocate at %s\n", __func__);
+               err = -ENOMEM;
+               goto err_out;
        }
+
+       as->pdir_page = page;
+       as->pte_count = cnt;
+
        SetPageReserved(as->pdir_page);
        pdir = page_address(as->pdir_page);
 
@@ -579,7 +604,17 @@ static int alloc_pdir(struct smmu_as *as)
        smmu_write(smmu, val, SMMU_TLB_FLUSH);
        FLUSH_SMMU_REGS(as->smmu);
 
+       spin_unlock_irqrestore(&as->lock, flags);
+
        return 0;
+
+err_out:
+       spin_unlock_irqrestore(&as->lock, flags);
+
+       devm_kfree(smmu->dev, cnt);
+       if (page)
+               __free_page(page);
+       return err;
 }
 
 static void __smmu_iommu_unmap(struct smmu_as *as, dma_addr_t iova)
@@ -771,30 +806,28 @@ out:
 
 static int smmu_iommu_domain_init(struct iommu_domain *domain)
 {
-       int i;
+       int i, err = -ENODEV;
        unsigned long flags;
        struct smmu_as *as;
        struct smmu_device *smmu = smmu_handle;
 
        /* Look for a free AS with lock held */
        for  (i = 0; i < smmu->num_as; i++) {
-               struct smmu_as *tmp = &smmu->as[i];
-
-               spin_lock_irqsave(&tmp->lock, flags);
-               if (!tmp->pdir_page) {
-                       as = tmp;
-                       goto found;
+               as = &smmu->as[i];
+               if (!as->pdir_page) {
+                       err = alloc_pdir(as);
+                       if (!err)
+                               goto found;
                }
-               spin_unlock_irqrestore(&tmp->lock, flags);
+               if (err != -EAGAIN)
+                       break;
        }
-       dev_err(smmu->dev, "no free AS\n");
-       return -ENODEV;
+       if (i == smmu->num_as)
+               dev_err(smmu->dev,  "no free AS\n");
+       return err;
 
 found:
-       if (alloc_pdir(as) < 0)
-               goto err_alloc_pdir;
-
-       spin_lock(&smmu->lock);
+       spin_lock_irqsave(&smmu->lock, flags);
 
        /* Update PDIR register */
        smmu_write(smmu, SMMU_PTB_ASID_CUR(as->asid), SMMU_PTB_ASID);
@@ -802,17 +835,18 @@ found:
                   SMMU_MK_PDIR(as->pdir_page, as->pdir_attr), SMMU_PTB_DATA);
        FLUSH_SMMU_REGS(smmu);
 
-       spin_unlock(&smmu->lock);
+       spin_unlock_irqrestore(&smmu->lock, flags);
 
-       spin_unlock_irqrestore(&as->lock, flags);
        domain->priv = as;
 
+       domain->geometry.aperture_start = smmu->iovmm_base;
+       domain->geometry.aperture_end   = smmu->iovmm_base +
+               smmu->page_count * SMMU_PAGE_SIZE - 1;
+       domain->geometry.force_aperture = true;
+
        dev_dbg(smmu->dev, "smmu_as@%p\n", as);
-       return 0;
 
-err_alloc_pdir:
-       spin_unlock_irqrestore(&as->lock, flags);
-       return -ENODEV;
+       return 0;
 }
 
 static void smmu_iommu_domain_destroy(struct iommu_domain *domain)
@@ -873,65 +907,73 @@ static int tegra_smmu_resume(struct device *dev)
 {
        struct smmu_device *smmu = dev_get_drvdata(dev);
        unsigned long flags;
+       int err;
 
        spin_lock_irqsave(&smmu->lock, flags);
-       smmu_setup_regs(smmu);
+       err = smmu_setup_regs(smmu);
        spin_unlock_irqrestore(&smmu->lock, flags);
-       return 0;
+       return err;
 }
 
 static int tegra_smmu_probe(struct platform_device *pdev)
 {
        struct smmu_device *smmu;
-       struct resource *regs, *regs2, *window;
        struct device *dev = &pdev->dev;
-       int i, err = 0;
+       int i, asids, err = 0;
+       dma_addr_t uninitialized_var(base);
+       size_t bytes, uninitialized_var(size);
 
        if (smmu_handle)
                return -EIO;
 
        BUILD_BUG_ON(PAGE_SHIFT != SMMU_PAGE_SHIFT);
 
-       regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       regs2 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
-       window = platform_get_resource(pdev, IORESOURCE_MEM, 2);
-       if (!regs || !regs2 || !window) {
-               dev_err(dev, "No SMMU resources\n");
+       if (of_property_read_u32(dev->of_node, "nvidia,#asids", &asids))
                return -ENODEV;
-       }
 
-       smmu = devm_kzalloc(dev, sizeof(*smmu), GFP_KERNEL);
+       bytes = sizeof(*smmu) + asids * sizeof(*smmu->as);
+       smmu = devm_kzalloc(dev, bytes, GFP_KERNEL);
        if (!smmu) {
                dev_err(dev, "failed to allocate smmu_device\n");
                return -ENOMEM;
        }
 
-       smmu->dev = dev;
-       smmu->num_as = SMMU_NUM_ASIDS;
-       smmu->iovmm_base = (unsigned long)window->start;
-       smmu->page_count = resource_size(window) >> SMMU_PAGE_SHIFT;
-       smmu->regs = devm_ioremap(dev, regs->start, resource_size(regs));
-       smmu->regs_ahbarb = devm_ioremap(dev, regs2->start,
-                                        resource_size(regs2));
-       if (!smmu->regs || !smmu->regs_ahbarb) {
-               dev_err(dev, "failed to remap SMMU registers\n");
-               err = -ENXIO;
-               goto fail;
+       for (i = 0; i < ARRAY_SIZE(smmu->regs); i++) {
+               struct resource *res;
+
+               res = platform_get_resource(pdev, IORESOURCE_MEM, i);
+               if (!res)
+                       return -ENODEV;
+               smmu->regs[i] = devm_request_and_ioremap(&pdev->dev, res);
+               if (!smmu->regs[i])
+                       return -EBUSY;
        }
 
+       err = of_get_dma_window(dev->of_node, NULL, 0, NULL, &base, &size);
+       if (err)
+               return -ENODEV;
+
+       if (size & SMMU_PAGE_MASK)
+               return -EINVAL;
+
+       size >>= SMMU_PAGE_SHIFT;
+       if (!size)
+               return -EINVAL;
+
+       smmu->ahb = of_parse_phandle(dev->of_node, "nvidia,ahb", 0);
+       if (!smmu->ahb)
+               return -ENODEV;
+
+       smmu->dev = dev;
+       smmu->num_as = asids;
+       smmu->iovmm_base = base;
+       smmu->page_count = size;
+
        smmu->translation_enable_0 = ~0;
        smmu->translation_enable_1 = ~0;
        smmu->translation_enable_2 = ~0;
        smmu->asid_security = 0;
 
-       smmu->as = devm_kzalloc(dev,
-                       sizeof(smmu->as[0]) * smmu->num_as, GFP_KERNEL);
-       if (!smmu->as) {
-               dev_err(dev, "failed to allocate smmu_as\n");
-               err = -ENOMEM;
-               goto fail;
-       }
-
        for (i = 0; i < smmu->num_as; i++) {
                struct smmu_as *as = &smmu->as[i];
 
@@ -945,57 +987,28 @@ static int tegra_smmu_probe(struct platform_device *pdev)
                INIT_LIST_HEAD(&as->client);
        }
        spin_lock_init(&smmu->lock);
-       smmu_setup_regs(smmu);
+       err = smmu_setup_regs(smmu);
+       if (err)
+               return err;
        platform_set_drvdata(pdev, smmu);
 
        smmu->avp_vector_page = alloc_page(GFP_KERNEL);
        if (!smmu->avp_vector_page)
-               goto fail;
+               return -ENOMEM;
 
        smmu_handle = smmu;
        return 0;
-
-fail:
-       if (smmu->avp_vector_page)
-               __free_page(smmu->avp_vector_page);
-       if (smmu->regs)
-               devm_iounmap(dev, smmu->regs);
-       if (smmu->regs_ahbarb)
-               devm_iounmap(dev, smmu->regs_ahbarb);
-       if (smmu && smmu->as) {
-               for (i = 0; i < smmu->num_as; i++) {
-                       if (smmu->as[i].pdir_page) {
-                               ClearPageReserved(smmu->as[i].pdir_page);
-                               __free_page(smmu->as[i].pdir_page);
-                       }
-               }
-               devm_kfree(dev, smmu->as);
-       }
-       devm_kfree(dev, smmu);
-       return err;
 }
 
 static int tegra_smmu_remove(struct platform_device *pdev)
 {
        struct smmu_device *smmu = platform_get_drvdata(pdev);
-       struct device *dev = smmu->dev;
+       int i;
 
        smmu_write(smmu, SMMU_CONFIG_DISABLE, SMMU_CONFIG);
-       platform_set_drvdata(pdev, NULL);
-       if (smmu->as) {
-               int i;
-
-               for (i = 0; i < smmu->num_as; i++)
-                       free_pdir(&smmu->as[i]);
-               devm_kfree(dev, smmu->as);
-       }
-       if (smmu->avp_vector_page)
-               __free_page(smmu->avp_vector_page);
-       if (smmu->regs)
-               devm_iounmap(dev, smmu->regs);
-       if (smmu->regs_ahbarb)
-               devm_iounmap(dev, smmu->regs_ahbarb);
-       devm_kfree(dev, smmu);
+       for (i = 0; i < smmu->num_as; i++)
+               free_pdir(&smmu->as[i]);
+       __free_page(smmu->avp_vector_page);
        smmu_handle = NULL;
        return 0;
 }
@@ -1005,6 +1018,14 @@ const struct dev_pm_ops tegra_smmu_pm_ops = {
        .resume         = tegra_smmu_resume,
 };
 
+#ifdef CONFIG_OF
+static struct of_device_id tegra_smmu_of_match[] __devinitdata = {
+       { .compatible = "nvidia,tegra30-smmu", },
+       { },
+};
+MODULE_DEVICE_TABLE(of, tegra_smmu_of_match);
+#endif
+
 static struct platform_driver tegra_smmu_driver = {
        .probe          = tegra_smmu_probe,
        .remove         = tegra_smmu_remove,
@@ -1012,6 +1033,7 @@ static struct platform_driver tegra_smmu_driver = {
                .owner  = THIS_MODULE,
                .name   = "tegra-smmu",
                .pm     = &tegra_smmu_pm_ops,
+               .of_match_table = of_match_ptr(tegra_smmu_of_match),
        },
 };
 
@@ -1031,4 +1053,5 @@ module_exit(tegra_smmu_exit);
 
 MODULE_DESCRIPTION("IOMMU API for SMMU in Tegra30");
 MODULE_AUTHOR("Hiroshi DOYU <hdoyu@nvidia.com>");
+MODULE_ALIAS("platform:tegra-smmu");
 MODULE_LICENSE("GPL v2");