iommu/tegra: smmu: Add device tree support for SMMU

The necessary info is expected to pass from DT.

For more precise resource reservation, there shouldn't be any
overlapping of register range between SMMU and MC. SMMU register
offset needs to be calculated correctly, based on its register bank.

Signed-off-by: Hiroshi DOYU <hdoyu@nvidia.com>
Acked-by: Stephen Warren <swarren@wwwdotorg.org>
Acked-by: Grant Likely <grant.likely@secretlab.ca>
Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
This commit is contained in:
Hiroshi Doyu 2012-06-25 14:23:55 +03:00 committed by Joerg Roedel
parent 4e0ee78f2a
commit 0760e8faa9
3 changed files with 117 additions and 55 deletions

View file

@ -0,0 +1,21 @@
NVIDIA Tegra 30 IOMMU H/W, SMMU (System Memory Management Unit)
Required properties:
- compatible : "nvidia,tegra30-smmu"
- reg : Should contain 3 register banks(address and length) for each
of the SMMU register blocks.
- interrupts : Should contain MC General interrupt.
- nvidia,#asids : # of ASIDs
- dma-window : IOVA start address and length.
- nvidia,ahb : phandle to the ahb bus connected to SMMU.
Example:
smmu {
compatible = "nvidia,tegra30-smmu";
reg = <0x7000f010 0x02c
0x7000f1f0 0x010
0x7000f228 0x05c>;
nvidia,#asids = <4>; /* # of ASIDs */
dma-window = <0 0x40000000>; /* IOVA start & length */
nvidia,ahb = <&ahb>;
};

View file

@ -158,7 +158,7 @@ config TEGRA_IOMMU_GART
config TEGRA_IOMMU_SMMU
bool "Tegra SMMU IOMMU Support"
depends on ARCH_TEGRA_3x_SOC
depends on ARCH_TEGRA_3x_SOC && TEGRA_AHB
select IOMMU_API
help
Enables support for remapping discontiguous physical memory

View file

@ -30,12 +30,15 @@
#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)
@ -111,12 +114,6 @@
#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
@ -136,6 +133,7 @@
#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)
@ -177,6 +175,8 @@
#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,7 +235,7 @@ 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;
@ -252,29 +252,47 @@ struct smmu_device {
unsigned long translation_enable_1;
unsigned long translation_enable_2;
unsigned long asid_security;
struct device_node *ahb;
};
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)
static inline void smmu_write(struct smmu_device *smmu, u32 val, size_t offs)
{
return readl(smmu->regs_ahbarb + offs);
}
static inline void ahb_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 +388,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 +416,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,
@ -873,52 +888,72 @@ 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 base;
size_t size;
const void *prop;
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");
return -ENODEV;
}
smmu = devm_kzalloc(dev, sizeof(*smmu), 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;
prop = of_get_property(dev->of_node, "nvidia,#asids", NULL);
if (!prop)
return -ENODEV;
asids = be32_to_cpup(prop);
if (!asids)
return -ENODEV;
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;
@ -945,7 +980,9 @@ 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)
goto fail;
platform_set_drvdata(pdev, smmu);
smmu->avp_vector_page = alloc_page(GFP_KERNEL);
@ -958,10 +995,6 @@ static int tegra_smmu_probe(struct platform_device *pdev)
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) {
@ -993,8 +1026,6 @@ static int tegra_smmu_remove(struct platform_device *pdev)
__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);
smmu_handle = NULL;
return 0;
@ -1005,6 +1036,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 +1051,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 +1071,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");