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:
Mitchel Humpherys 2014-07-23 17:35:07 -07:00 committed by David Keitel
parent c0a42b2089
commit 59360e7926
2 changed files with 94 additions and 17 deletions

View file

@ -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 {

View file

@ -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;
}