aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinaro CI <ci_notify@linaro.org>2018-04-20 21:05:30 +0000
committerLinaro CI <ci_notify@linaro.org>2018-04-20 21:05:30 +0000
commit1d20a54f7c41d2decb5367e9188330902b2316d4 (patch)
tree517afbd226519a23e44918c2cf1db2dea0ec05f3
parent47accf25a39694ee93e90080f8bcae3427f6a0d6 (diff)
parent153c14656d9e5c53940b80efa0d17fc7d9a6d71f (diff)
Merge remote-tracking branch 'iommu/v4.15/tracking-qcomlt-iommu' into integration-linux-qcomlt
-rw-r--r--Documentation/devicetree/bindings/iommu/arm,smmu.txt42
-rw-r--r--arch/arm64/include/asm/io.h8
-rw-r--r--drivers/iommu/arm-smmu.c178
3 files changed, 215 insertions, 13 deletions
diff --git a/Documentation/devicetree/bindings/iommu/arm,smmu.txt b/Documentation/devicetree/bindings/iommu/arm,smmu.txt
index 8a6ffce12af5..5d8e79775fae 100644
--- a/Documentation/devicetree/bindings/iommu/arm,smmu.txt
+++ b/Documentation/devicetree/bindings/iommu/arm,smmu.txt
@@ -17,6 +17,7 @@ conditions.
"arm,mmu-401"
"arm,mmu-500"
"cavium,smmu-v2"
+ "qcom,msm8996-smmu-v2"
depending on the particular implementation and/or the
version of the architecture implemented.
@@ -71,6 +72,31 @@ conditions.
or using stream matching with #iommu-cells = <2>, and
may be ignored if present in such cases.
+- clock-names: Should be "tcu" and "iface" for "arm,mmu-400",
+ "arm,mmu-401" and "arm,mmu-500"
+
+ Should be "bus", and "iface" for "qcom,msm8996-smmu-v2"
+ implementation.
+
+ "tcu" clock is required for smmu's register access using the
+ programming interface and ptw for downstream bus access. This
+ clock is also used for access to the TBU connected to the
+ master locally. Sometimes however, TBU is clocked along with
+ the master.
+ "bus" clock for "qcom,msm8996-smmu-v2" is requierd for downstream
+ bus access and for the smmu ptw.
+
+ "iface" clock is required to access the TCU's programming
+ interface, apart from the "tcu" clock.
+
+- clocks: Phandles for respective clocks described by clock-names.
+
+- power-domains: Phandles to SMMU's power domain specifier. This is
+ required even if SMMU belongs to the master's power
+ domain, as the SMMU will have to be enabled and
+ accessed before master gets enabled and linked to its
+ SMMU.
+
** Deprecated properties:
- mmu-masters (deprecated in favour of the generic "iommus" binding) :
@@ -95,6 +121,10 @@ conditions.
<0 36 4>,
<0 37 4>;
#iommu-cells = <1>;
+ clocks = <&gcc GCC_SMMU_CFG_CLK>,
+ <&gcc GCC_APSS_TCU_CLK>;
+
+ clock-names = "iface", "tcu";
};
/* device with two stream IDs, 0 and 7 */
@@ -137,3 +167,15 @@ conditions.
iommu-map = <0 &smmu3 0 0x400>;
...
};
+
+ /* Qcom's arm,smmu-v2 implementation for msm8996 */
+ smmu4: iommu {
+ compatible = "qcom,msm8996-smmu-v2";
+ ...
+ #iommu-cells = <1>;
+ power-domains = <&mmcc MDSS_GDSC>;
+
+ clocks = <&mmcc SMMU_MDP_AXI_CLK>,
+ <&mmcc SMMU_MDP_AHB_CLK>;
+ clock-names = "bus", "iface";
+ };
diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h
index 35b2e50f17fb..3be829780fad 100644
--- a/arch/arm64/include/asm/io.h
+++ b/arch/arm64/include/asm/io.h
@@ -39,25 +39,25 @@
#define __raw_writeb __raw_writeb
static inline void __raw_writeb(u8 val, volatile void __iomem *addr)
{
- asm volatile("strb %w0, [%1]" : : "rZ" (val), "r" (addr));
+ asm volatile("strb %w0, [%1]" : : "r" (val), "r" (addr));
}
#define __raw_writew __raw_writew
static inline void __raw_writew(u16 val, volatile void __iomem *addr)
{
- asm volatile("strh %w0, [%1]" : : "rZ" (val), "r" (addr));
+ asm volatile("strh %w0, [%1]" : : "r" (val), "r" (addr));
}
#define __raw_writel __raw_writel
static inline void __raw_writel(u32 val, volatile void __iomem *addr)
{
- asm volatile("str %w0, [%1]" : : "rZ" (val), "r" (addr));
+ asm volatile("str %w0, [%1]" : : "r" (val), "r" (addr));
}
#define __raw_writeq __raw_writeq
static inline void __raw_writeq(u64 val, volatile void __iomem *addr)
{
- asm volatile("str %x0, [%1]" : : "rZ" (val), "r" (addr));
+ asm volatile("str %0, [%1]" : : "r" (val), "r" (addr));
}
#define __raw_readb __raw_readb
diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c
index 69e7c60792a8..e6920d32ac9e 100644
--- a/drivers/iommu/arm-smmu.c
+++ b/drivers/iommu/arm-smmu.c
@@ -48,6 +48,7 @@
#include <linux/of_iommu.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
@@ -118,6 +119,7 @@ enum arm_smmu_implementation {
GENERIC_SMMU,
ARM_MMU500,
CAVIUM_SMMUV2,
+ QCOM_MSM8996_SMMUV2,
};
struct arm_smmu_s2cr {
@@ -205,6 +207,9 @@ struct arm_smmu_device {
u32 num_global_irqs;
u32 num_context_irqs;
unsigned int *irqs;
+ int num_clks;
+ struct clk **clocks;
+ const char * const *clk_names;
u32 cavium_id_base; /* Specific to Cavium */
@@ -284,6 +289,32 @@ static void parse_driver_options(struct arm_smmu_device *smmu)
} while (arm_smmu_options[++i].opt);
}
+static int arm_smmu_enable_clocks(struct arm_smmu_device *smmu)
+{
+ int i, ret = 0;
+
+ for (i = 0; i < smmu->num_clks; ++i) {
+ ret = clk_prepare_enable(smmu->clocks[i]);
+ if (ret) {
+ dev_err(smmu->dev, "Couldn't enable %s clock\n",
+ smmu->clk_names[i]);
+ while (i--)
+ clk_disable_unprepare(smmu->clocks[i]);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static void arm_smmu_disable_clocks(struct arm_smmu_device *smmu)
+{
+ int i = smmu->num_clks;
+
+ while (i--)
+ clk_disable_unprepare(smmu->clocks[i]);
+}
+
static struct device_node *dev_get_dev_node(struct device *dev)
{
if (dev_is_pci(dev)) {
@@ -910,11 +941,15 @@ static void arm_smmu_destroy_domain_context(struct iommu_domain *domain)
struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
struct arm_smmu_device *smmu = smmu_domain->smmu;
struct arm_smmu_cfg *cfg = &smmu_domain->cfg;
- int irq;
+ int irq, ret;
if (!smmu || domain->type == IOMMU_DOMAIN_IDENTITY)
return;
+ ret = pm_runtime_get_sync(smmu->dev);
+ if (ret)
+ return;
+
/*
* Disable the context bank and free the page tables before freeing
* it.
@@ -929,6 +964,8 @@ static void arm_smmu_destroy_domain_context(struct iommu_domain *domain)
free_io_pgtable_ops(smmu_domain->pgtbl_ops);
__arm_smmu_free_bitmap(smmu->context_map, cfg->cbndx);
+
+ pm_runtime_put_sync(smmu->dev);
}
static struct iommu_domain *arm_smmu_domain_alloc(unsigned type)
@@ -1244,12 +1281,18 @@ static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova,
static size_t arm_smmu_unmap(struct iommu_domain *domain, unsigned long iova,
size_t size)
{
- struct io_pgtable_ops *ops = to_smmu_domain(domain)->pgtbl_ops;
+ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
+ struct io_pgtable_ops *ops = smmu_domain->pgtbl_ops;
+ size_t ret;
if (!ops)
return 0;
- return ops->unmap(ops, iova, size);
+ pm_runtime_get_sync(smmu_domain->smmu->dev);
+ ret = ops->unmap(ops, iova, size);
+ pm_runtime_put_sync(smmu_domain->smmu->dev);
+
+ return ret;
}
static void arm_smmu_iotlb_sync(struct iommu_domain *domain)
@@ -1357,6 +1400,7 @@ static int arm_smmu_add_device(struct device *dev)
struct arm_smmu_device *smmu;
struct arm_smmu_master_cfg *cfg;
struct iommu_fwspec *fwspec = dev->iommu_fwspec;
+ struct device_link *link = NULL;
int i, ret;
if (using_legacy_binding) {
@@ -1404,12 +1448,30 @@ static int arm_smmu_add_device(struct device *dev)
while (i--)
cfg->smendx[i] = INVALID_SMENDX;
- ret = arm_smmu_master_alloc_smes(dev);
+ ret = pm_runtime_get_sync(smmu->dev);
if (ret)
goto out_cfg_free;
+ ret = arm_smmu_master_alloc_smes(dev);
+ if (ret) {
+ pm_runtime_put_sync(smmu->dev);
+ goto out_cfg_free;
+ }
+
iommu_device_link(&smmu->iommu, dev);
+ pm_runtime_put_sync(smmu->dev);
+
+ /*
+ * Establish the link between smmu and master, so that the
+ * smmu gets runtime enabled/disabled as per the master's
+ * needs.
+ */
+ link = device_link_add(dev, smmu->dev, DL_FLAG_PM_RUNTIME);
+ if (!link)
+ dev_warn(smmu->dev, "Unable to create device link between %s and %s\n",
+ dev_name(smmu->dev), dev_name(dev));
+
return 0;
out_cfg_free:
@@ -1424,7 +1486,7 @@ static void arm_smmu_remove_device(struct device *dev)
struct iommu_fwspec *fwspec = dev->iommu_fwspec;
struct arm_smmu_master_cfg *cfg;
struct arm_smmu_device *smmu;
-
+ int ret;
if (!fwspec || fwspec->ops != &arm_smmu_ops)
return;
@@ -1432,8 +1494,21 @@ static void arm_smmu_remove_device(struct device *dev)
cfg = fwspec->iommu_priv;
smmu = cfg->smmu;
+ /*
+ * The device link between the master device and
+ * smmu is already purged at this point.
+ * So enable the power to smmu explicitly.
+ */
+
+ ret = pm_runtime_get_sync(smmu->dev);
+ if (ret)
+ return;
+
iommu_device_unlink(&smmu->iommu, dev);
arm_smmu_master_free_smes(fwspec);
+
+ pm_runtime_put_sync(smmu->dev);
+
iommu_group_remove_device(dev);
kfree(fwspec->iommu_priv);
iommu_fwspec_free(dev);
@@ -1685,6 +1760,36 @@ static int arm_smmu_id_size_to_bits(int size)
}
}
+static int arm_smmu_init_clocks(struct arm_smmu_device *smmu)
+{
+ int i, err;
+ struct device *dev = smmu->dev;
+
+ if (smmu->num_clks < 1)
+ return 0;
+
+ smmu->clocks = devm_kcalloc(dev, smmu->num_clks,
+ sizeof(*smmu->clocks), GFP_KERNEL);
+ if (!smmu->clocks)
+ return -ENOMEM;
+
+ for (i = 0; i < smmu->num_clks; i++) {
+ const char *cname = smmu->clk_names[i];
+ struct clk *c = devm_clk_get(dev, cname);
+
+ if (IS_ERR(c)) {
+ err = PTR_ERR(c);
+ if (err != -EPROBE_DEFER)
+ dev_err(dev, "Couldn't get clock: %s", cname);
+
+ return err;
+ }
+ smmu->clocks[i] = c;
+ }
+
+ return 0;
+}
+
static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu)
{
unsigned long size;
@@ -1897,17 +2002,40 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu)
struct arm_smmu_match_data {
enum arm_smmu_arch_version version;
enum arm_smmu_implementation model;
+ const char * const *clks;
+ int num_clks;
};
#define ARM_SMMU_MATCH_DATA(name, ver, imp) \
-static struct arm_smmu_match_data name = { .version = ver, .model = imp }
+static const struct arm_smmu_match_data name = { .version = ver, .model = imp }
ARM_SMMU_MATCH_DATA(smmu_generic_v1, ARM_SMMU_V1, GENERIC_SMMU);
ARM_SMMU_MATCH_DATA(smmu_generic_v2, ARM_SMMU_V2, GENERIC_SMMU);
ARM_SMMU_MATCH_DATA(arm_mmu401, ARM_SMMU_V1_64K, GENERIC_SMMU);
-ARM_SMMU_MATCH_DATA(arm_mmu500, ARM_SMMU_V2, ARM_MMU500);
ARM_SMMU_MATCH_DATA(cavium_smmuv2, ARM_SMMU_V2, CAVIUM_SMMUV2);
+static const char * const arm_mmu500_clks[] = {
+ "tcu", "iface",
+};
+
+static const struct arm_smmu_match_data arm_mmu500 = {
+ .version = ARM_SMMU_V2,
+ .model = ARM_MMU500,
+ .clks = arm_mmu500_clks,
+ .num_clks = ARRAY_SIZE(arm_mmu500_clks),
+};
+
+static const char * const qcom_msm8996_smmuv2_clks[] = {
+ "bus", "iface",
+};
+
+static const struct arm_smmu_match_data qcom_msm8996_smmuv2 = {
+ .version = ARM_SMMU_V2,
+ .model = QCOM_MSM8996_SMMUV2,
+ .clks = qcom_msm8996_smmuv2_clks,
+ .num_clks = ARRAY_SIZE(qcom_msm8996_smmuv2_clks),
+};
+
static const struct of_device_id arm_smmu_of_match[] = {
{ .compatible = "arm,smmu-v1", .data = &smmu_generic_v1 },
{ .compatible = "arm,smmu-v2", .data = &smmu_generic_v2 },
@@ -1915,6 +2043,7 @@ static const struct of_device_id arm_smmu_of_match[] = {
{ .compatible = "arm,mmu-401", .data = &arm_mmu401 },
{ .compatible = "arm,mmu-500", .data = &arm_mmu500 },
{ .compatible = "cavium,smmu-v2", .data = &cavium_smmuv2 },
+ { .compatible = "qcom,msm8996-smmu-v2", .data = &qcom_msm8996_smmuv2 },
{ },
};
MODULE_DEVICE_TABLE(of, arm_smmu_of_match);
@@ -2001,6 +2130,8 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev,
data = of_device_get_match_data(dev);
smmu->version = data->version;
smmu->model = data->model;
+ smmu->clk_names = data->clks;
+ smmu->num_clks = data->num_clks;
parse_driver_options(smmu);
@@ -2099,6 +2230,17 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
smmu->irqs[i] = irq;
}
+ err = arm_smmu_init_clocks(smmu);
+ if (err)
+ return err;
+
+ platform_set_drvdata(pdev, smmu);
+ pm_runtime_enable(dev);
+
+ err = pm_runtime_get_sync(dev);
+ if (err)
+ return err;
+
err = arm_smmu_device_cfg_probe(smmu);
if (err)
return err;
@@ -2140,9 +2282,9 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
return err;
}
- platform_set_drvdata(pdev, smmu);
arm_smmu_device_reset(smmu);
arm_smmu_test_smr_masks(smmu);
+ pm_runtime_put_sync(dev);
/*
* For ACPI and generic DT bindings, an SMMU will be probed before
@@ -2181,6 +2323,8 @@ static int arm_smmu_device_remove(struct platform_device *pdev)
/* Turn the thing off */
writel(sCR0_CLIENTPD, ARM_SMMU_GR0_NS(smmu) + ARM_SMMU_GR0_sCR0);
+ pm_runtime_force_suspend(smmu->dev);
+
return 0;
}
@@ -2192,18 +2336,34 @@ static void arm_smmu_device_shutdown(struct platform_device *pdev)
static int __maybe_unused arm_smmu_pm_resume(struct device *dev)
{
struct arm_smmu_device *smmu = dev_get_drvdata(dev);
+ int ret;
+
+ ret = arm_smmu_enable_clocks(smmu);
+ if (ret)
+ return ret;
arm_smmu_device_reset(smmu);
return 0;
}
-static SIMPLE_DEV_PM_OPS(arm_smmu_pm_ops, NULL, arm_smmu_pm_resume);
+static int __maybe_unused arm_smmu_suspend(struct device *dev)
+{
+ struct arm_smmu_device *smmu = dev_get_drvdata(dev);
+
+ arm_smmu_disable_clocks(smmu);
+
+ return 0;
+}
+
+static UNIVERSAL_DEV_PM_OPS(arm_smmu_pm_ops, arm_smmu_suspend,
+ arm_smmu_pm_resume, NULL);
static struct platform_driver arm_smmu_driver = {
.driver = {
.name = "arm-smmu",
.of_match_table = of_match_ptr(arm_smmu_of_match),
.pm = &arm_smmu_pm_ops,
+ .pm = &arm_smmu_pm_ops,
},
.probe = arm_smmu_device_probe,
.remove = arm_smmu_device_remove,