iommu/arm-smmu: add support for specifying regulators
On some power-constrained platforms it's useful to disable power when a device is not in use. Add support for specifying regulators for SMMUs and only leave power on as long as the SMMU is in use (attached). Change-Id: I87191d325423f160ddd4b71f5bf3a92f4942b821 Signed-off-by: Mitchel Humpherys <mitchelh@codeaurora.org>
This commit is contained in:
parent
c0a42b2089
commit
59360e7926
2 changed files with 94 additions and 17 deletions
|
@ -66,6 +66,9 @@ conditions.
|
|||
Documentation/devicetree/bindings/clock/clock-bindings.txt
|
||||
for more info.
|
||||
|
||||
- vdd-supply : Phandle of the regulator that should be powered on during
|
||||
SMMU register access.
|
||||
|
||||
Example:
|
||||
|
||||
smmu {
|
||||
|
|
|
@ -394,6 +394,11 @@ struct arm_smmu_device {
|
|||
|
||||
int num_clocks;
|
||||
struct clk **clocks;
|
||||
|
||||
struct regulator *gdsc;
|
||||
|
||||
struct mutex attach_lock;
|
||||
unsigned int attach_count;
|
||||
};
|
||||
|
||||
struct arm_smmu_cfg {
|
||||
|
@ -617,6 +622,22 @@ static void arm_smmu_disable_clocks(struct arm_smmu_device *smmu)
|
|||
clk_disable_unprepare(smmu->clocks[i]);
|
||||
}
|
||||
|
||||
static int arm_smmu_enable_regulators(struct arm_smmu_device *smmu)
|
||||
{
|
||||
if (!smmu->gdsc)
|
||||
return 0;
|
||||
|
||||
return regulator_enable(smmu->gdsc);
|
||||
}
|
||||
|
||||
static int arm_smmu_disable_regulators(struct arm_smmu_device *smmu)
|
||||
{
|
||||
if (!smmu->gdsc)
|
||||
return 0;
|
||||
|
||||
return regulator_disable(smmu->gdsc);
|
||||
}
|
||||
|
||||
/* Wait for any pending TLB invalidations to complete */
|
||||
static void arm_smmu_tlb_sync(struct arm_smmu_device *smmu)
|
||||
{
|
||||
|
@ -1249,6 +1270,8 @@ static void arm_smmu_domain_remove_master(struct arm_smmu_domain *smmu_domain,
|
|||
arm_smmu_disable_clocks(smmu);
|
||||
}
|
||||
|
||||
static void arm_smmu_device_reset(struct arm_smmu_device *smmu);
|
||||
|
||||
static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
|
@ -1267,7 +1290,15 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
|
|||
return -EEXIST;
|
||||
}
|
||||
|
||||
arm_smmu_enable_clocks(smmu);
|
||||
mutex_lock(&smmu->attach_lock);
|
||||
if (!smmu->attach_count++) {
|
||||
arm_smmu_enable_regulators(smmu);
|
||||
arm_smmu_enable_clocks(smmu);
|
||||
arm_smmu_device_reset(smmu);
|
||||
} else {
|
||||
arm_smmu_enable_clocks(smmu);
|
||||
}
|
||||
mutex_unlock(&smmu->attach_lock);
|
||||
|
||||
/*
|
||||
* Sanity check the domain. We don't support domains across
|
||||
|
@ -1278,7 +1309,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
|
|||
/* Now that we have a master, we can finalise the domain */
|
||||
ret = arm_smmu_init_domain_context(domain, smmu);
|
||||
if (IS_ERR_VALUE(ret))
|
||||
goto disable_clocks;
|
||||
goto err_disable_clocks;
|
||||
|
||||
dom_smmu = smmu_domain->smmu;
|
||||
}
|
||||
|
@ -1288,28 +1319,46 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
|
|||
"cannot attach to SMMU %s whilst already attached to domain on SMMU %s\n",
|
||||
dev_name(smmu_domain->smmu->dev), dev_name(smmu->dev));
|
||||
ret = -EINVAL;
|
||||
goto disable_clocks;
|
||||
goto err_disable_clocks;
|
||||
}
|
||||
|
||||
/* Looks ok, so add the device to the domain */
|
||||
cfg = find_smmu_master_cfg(dev);
|
||||
if (!cfg) {
|
||||
ret = -ENODEV;
|
||||
goto disable_clocks;
|
||||
goto err_disable_clocks;
|
||||
}
|
||||
|
||||
ret = arm_smmu_domain_add_master(smmu_domain, cfg);
|
||||
if (!ret)
|
||||
dev->archdata.iommu = domain;
|
||||
disable_clocks:
|
||||
arm_smmu_disable_clocks(smmu);
|
||||
return ret;
|
||||
|
||||
err_disable_clocks:
|
||||
arm_smmu_disable_clocks(smmu);
|
||||
mutex_lock(&smmu->attach_lock);
|
||||
if (!--smmu->attach_count)
|
||||
arm_smmu_disable_regulators(smmu);
|
||||
mutex_unlock(&smmu->attach_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void arm_smmu_power_off(struct arm_smmu_device *smmu)
|
||||
{
|
||||
/* Turn the thing off */
|
||||
arm_smmu_enable_clocks(smmu);
|
||||
writel_relaxed(sCR0_CLIENTPD,
|
||||
ARM_SMMU_GR0_NS(smmu) + ARM_SMMU_GR0_sCR0);
|
||||
arm_smmu_disable_clocks(smmu);
|
||||
arm_smmu_disable_regulators(smmu);
|
||||
}
|
||||
|
||||
static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev)
|
||||
{
|
||||
struct arm_smmu_domain *smmu_domain = domain->priv;
|
||||
struct arm_smmu_master_cfg *cfg;
|
||||
struct arm_smmu_device *smmu = smmu_domain->smmu;
|
||||
|
||||
cfg = find_smmu_master_cfg(dev);
|
||||
if (!cfg)
|
||||
|
@ -1317,6 +1366,10 @@ static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev)
|
|||
|
||||
dev->archdata.iommu = NULL;
|
||||
arm_smmu_domain_remove_master(smmu_domain, cfg);
|
||||
mutex_lock(&smmu->attach_lock);
|
||||
if (!--smmu->attach_count)
|
||||
arm_smmu_power_off(smmu);
|
||||
mutex_unlock(&smmu->attach_lock);
|
||||
}
|
||||
|
||||
static bool arm_smmu_pte_is_contiguous_range(unsigned long addr,
|
||||
|
@ -1783,6 +1836,20 @@ static int arm_smmu_id_size_to_bits(int size)
|
|||
}
|
||||
}
|
||||
|
||||
static int arm_smmu_init_regulators(struct arm_smmu_device *smmu)
|
||||
{
|
||||
struct device *dev = smmu->dev;
|
||||
|
||||
if (!of_get_property(dev->of_node, "vdd-supply", NULL))
|
||||
return 0;
|
||||
|
||||
smmu->gdsc = devm_regulator_get(dev, "vdd");
|
||||
if (IS_ERR(smmu->gdsc))
|
||||
return PTR_ERR(smmu->gdsc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int arm_smmu_init_clocks(struct arm_smmu_device *smmu)
|
||||
{
|
||||
const char *cname;
|
||||
|
@ -2008,6 +2075,7 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev)
|
|||
return -ENOMEM;
|
||||
}
|
||||
smmu->dev = dev;
|
||||
mutex_init(&smmu->attach_lock);
|
||||
|
||||
of_id = of_match_node(arm_smmu_of_match, dev->of_node);
|
||||
smmu->version = (enum arm_smmu_arch_version)of_id->data;
|
||||
|
@ -2070,15 +2138,21 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev)
|
|||
}
|
||||
dev_notice(dev, "registered %d master devices\n", i);
|
||||
|
||||
err = arm_smmu_init_regulators(smmu);
|
||||
if (err)
|
||||
goto out_put_masters;
|
||||
|
||||
err = arm_smmu_init_clocks(smmu);
|
||||
if (err)
|
||||
goto out_put_masters;
|
||||
|
||||
arm_smmu_enable_regulators(smmu);
|
||||
arm_smmu_enable_clocks(smmu);
|
||||
|
||||
err = arm_smmu_device_cfg_probe(smmu);
|
||||
arm_smmu_disable_clocks(smmu);
|
||||
arm_smmu_disable_regulators(smmu);
|
||||
if (err)
|
||||
goto out_disable_clocks;
|
||||
goto out_put_masters;
|
||||
|
||||
parse_driver_options(smmu);
|
||||
|
||||
|
@ -2088,7 +2162,7 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev)
|
|||
"found only %d context interrupt(s) but %d required\n",
|
||||
smmu->num_context_irqs, smmu->num_context_banks);
|
||||
err = -ENODEV;
|
||||
goto out_disable_clocks;
|
||||
goto out_put_masters;
|
||||
}
|
||||
|
||||
for (i = 0; i < smmu->num_global_irqs; ++i) {
|
||||
|
@ -2109,17 +2183,12 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev)
|
|||
list_add(&smmu->list, &arm_smmu_devices);
|
||||
spin_unlock(&arm_smmu_devices_lock);
|
||||
|
||||
arm_smmu_device_reset(smmu);
|
||||
arm_smmu_disable_clocks(smmu);
|
||||
return 0;
|
||||
|
||||
out_free_irqs:
|
||||
while (i--)
|
||||
free_irq(smmu->irqs[i], smmu);
|
||||
|
||||
out_disable_clocks:
|
||||
arm_smmu_disable_clocks(smmu);
|
||||
|
||||
out_put_masters:
|
||||
for (node = rb_first(&smmu->masters); node; node = rb_next(node)) {
|
||||
struct arm_smmu_master *master
|
||||
|
@ -2162,10 +2231,15 @@ static int arm_smmu_device_remove(struct platform_device *pdev)
|
|||
for (i = 0; i < smmu->num_global_irqs; ++i)
|
||||
free_irq(smmu->irqs[i], smmu);
|
||||
|
||||
/* Turn the thing off */
|
||||
arm_smmu_enable_clocks(smmu);
|
||||
writel(sCR0_CLIENTPD, ARM_SMMU_GR0_NS(smmu) + ARM_SMMU_GR0_sCR0);
|
||||
arm_smmu_disable_clocks(smmu);
|
||||
mutex_lock(&smmu->attach_lock);
|
||||
/*
|
||||
* If all devices weren't detached for some reason, we're
|
||||
* still powered on. Power off now.
|
||||
*/
|
||||
if (smmu->attach_count)
|
||||
arm_smmu_power_off(smmu);
|
||||
mutex_unlock(&smmu->attach_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue