diff options
author | Linaro CI <ci_notify@linaro.org> | 2018-04-20 21:05:30 +0000 |
---|---|---|
committer | Linaro CI <ci_notify@linaro.org> | 2018-04-20 21:05:30 +0000 |
commit | 1d20a54f7c41d2decb5367e9188330902b2316d4 (patch) | |
tree | 517afbd226519a23e44918c2cf1db2dea0ec05f3 | |
parent | 47accf25a39694ee93e90080f8bcae3427f6a0d6 (diff) | |
parent | 153c14656d9e5c53940b80efa0d17fc7d9a6d71f (diff) |
Merge remote-tracking branch 'iommu/v4.15/tracking-qcomlt-iommu' into integration-linux-qcomlt
-rw-r--r-- | Documentation/devicetree/bindings/iommu/arm,smmu.txt | 42 | ||||
-rw-r--r-- | arch/arm64/include/asm/io.h | 8 | ||||
-rw-r--r-- | drivers/iommu/arm-smmu.c | 178 |
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, |