diff mbox

[v2,07/17] soc: tegra: pmc: Add generic PM domain support

Message ID 1426162518-7405-8-git-send-email-vinceh@nvidia.com
State Superseded, archived
Headers show

Commit Message

Vince Hsu March 12, 2015, 12:15 p.m. UTC
From: Thierry Reding <treding@nvidia.com>

The PM domains are populated from DT, and the PM domain consumer devices are
also bound to their relevant PM domains by DT.

Signed-off-by: Thierry Reding <treding@nvidia.com>
[vinceh: make changes based on Thierry and Peter's suggestions]
Signed-off-by: Vince Hsu <vinceh@nvidia.com>
---
v2: revise comment in tegra_powergate_remove_clamping()
    address Alex's comments

 drivers/soc/tegra/pmc.c                     | 589 +++++++++++++++++++++++++++-
 include/dt-bindings/power/tegra-powergate.h |  30 ++
 2 files changed, 617 insertions(+), 2 deletions(-)
 create mode 100644 include/dt-bindings/power/tegra-powergate.h

Comments

Kevin Hilman April 6, 2015, 10:37 p.m. UTC | #1
Hi Vince,

Vince Hsu <vinceh@nvidia.com> writes:

> From: Thierry Reding <treding@nvidia.com>
>
> The PM domains are populated from DT, and the PM domain consumer devices are
> also bound to their relevant PM domains by DT.
>
> Signed-off-by: Thierry Reding <treding@nvidia.com>
> [vinceh: make changes based on Thierry and Peter's suggestions]
> Signed-off-by: Vince Hsu <vinceh@nvidia.com>
> ---
> v2: revise comment in tegra_powergate_remove_clamping()
>     address Alex's comments

Sorry for the late review..., somehow I just noticed this while
reviewing some other PM domain support.

It's nice to see this migrating to PM domains.  Some comments below...

[...]

>  /**
>   * struct tegra_pmc - NVIDIA Tegra PMC
> + * @dev: pointer to parent device
>   * @base: pointer to I/O remapped register region
>   * @clk: pointer to pclk clock
> + * @soc: SoC-specific data
>   * @rate: currently configured rate of pclk
>   * @suspend_mode: lowest suspend mode available
>   * @cpu_good_time: CPU power good time (in microseconds)
> @@ -126,7 +158,9 @@ struct tegra_pmc_soc {
>   * @cpu_pwr_good_en: CPU power good signal is enabled
>   * @lp0_vec_phys: physical base address of the LP0 warm boot code
>   * @lp0_vec_size: size of the LP0 warm boot code
> + * @powergates: list of power gates
>   * @powergates_lock: mutex for power gate register access
> + * @nb: bus notifier for generic power domains
>   */
>  struct tegra_pmc {
>  	struct device *dev;
> @@ -150,7 +184,12 @@ struct tegra_pmc {
>  	u32 lp0_vec_phys;
>  	u32 lp0_vec_size;
>  
> +	struct tegra_powergate *powergates;
>  	struct mutex powergates_lock;
> +	struct notifier_block nb;

I don't see these notifiers used anywhere in this series.  What is the
intended use here?   There have been some other discussions about how to
do this more generically for  PM domains[1], so I'd prefer not to see this
in SoC specific PM domains.  In this case, it appears unused, so should
be fine to drop (for now.)

[...]

> +static int tegra_pmc_powergate_power_off(struct generic_pm_domain *domain)
> +{
> +	struct tegra_powergate *powergate = to_powergate(domain);
> +	struct tegra_pmc *pmc = powergate->pmc;
> +	int err;
> +
> +	dev_dbg(pmc->dev, "> %s(domain=%p)\n", __func__, domain);
> +	dev_dbg(pmc->dev, "  name: %s\n", domain->name);
> +
> +	/* never turn off these partitions */
> +	switch (powergate->id) {
> +	case TEGRA_POWERGATE_CPU:
> +	case TEGRA_POWERGATE_CPU1:
> +	case TEGRA_POWERGATE_CPU2:
> +	case TEGRA_POWERGATE_CPU3:
> +	case TEGRA_POWERGATE_CPU0:
> +	case TEGRA_POWERGATE_C0NC:
> +	case TEGRA_POWERGATE_IRAM:
> +		dev_dbg(pmc->dev, "not disabling always-on partition %s\n",
> +			domain->name);
> +		err = -EINVAL;
> +		goto out;
> +	}

You're re-inventing the per-device QoS flag: PM_QOS_FLAG_NO_POWER_OFF
which could be used here to prevent ->power_off from ever being called.

Alternately, if this really a static configuraion, why even register the
->power_off hook for these domains in the first place?

[...]

> +static int tegra_pmc_powergate_init_subdomain(struct tegra_pmc *pmc)
> +{
> +	struct tegra_powergate *powergate;
> +
> +	list_for_each_entry(powergate, &pmc->powergate_list, head) {
> +		struct device_node *pdn;
> +		struct tegra_powergate *parent = NULL;
> +		struct tegra_powergate *temp;
> +		int err;
> +
> +		pdn = of_parse_phandle(powergate->of_node, "depend-on", 0);
> +		if (!pdn)
> +			continue;

I'm not really following the need for the "depend-on" property here.

Looking at the example .dtsi files in this series, it seems to me what
is being described is nested hardware power domains, which genpd already
supports.  Any reason you're not using that build-in support?

[...]

> @@ -831,12 +1405,19 @@ static int tegra_pmc_probe(struct platform_device *pdev)
>  
>  	tegra_pmc_init_tsense_reset(pmc);
>  
> +	if (IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)) {
> +		err = tegra_powergate_init(pmc);
> +		if (err < 0)
> +			return err;
> +	}

On many SoCs there's some special handling for the !CONFIG_PM case here
such that all the PM domains are enabled by default in case they are not
enabled by the bootloader.

>  	if (IS_ENABLED(CONFIG_DEBUG_FS)) {
>  		err = tegra_powergate_debugfs_init();
>  		if (err < 0)
>  			return err;
>  	}
>  
> +	dev_dbg(&pdev->dev, "< %s()\n", __func__);
>  	return 0;
>  }

Kevin

[1] http://lists.infradead.org/pipermail/linux-arm-kernel/2014-November/299345.html
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Thierry Reding April 8, 2015, 8:06 a.m. UTC | #2
On Mon, Apr 06, 2015 at 03:37:37PM -0700, Kevin Hilman wrote:
> Hi Vince,
> 
> Vince Hsu <vinceh@nvidia.com> writes:
[...]
> > +static int tegra_pmc_powergate_power_off(struct generic_pm_domain *domain)
> > +{
> > +	struct tegra_powergate *powergate = to_powergate(domain);
> > +	struct tegra_pmc *pmc = powergate->pmc;
> > +	int err;
> > +
> > +	dev_dbg(pmc->dev, "> %s(domain=%p)\n", __func__, domain);
> > +	dev_dbg(pmc->dev, "  name: %s\n", domain->name);
> > +
> > +	/* never turn off these partitions */
> > +	switch (powergate->id) {
> > +	case TEGRA_POWERGATE_CPU:
> > +	case TEGRA_POWERGATE_CPU1:
> > +	case TEGRA_POWERGATE_CPU2:
> > +	case TEGRA_POWERGATE_CPU3:
> > +	case TEGRA_POWERGATE_CPU0:
> > +	case TEGRA_POWERGATE_C0NC:
> > +	case TEGRA_POWERGATE_IRAM:
> > +		dev_dbg(pmc->dev, "not disabling always-on partition %s\n",
> > +			domain->name);
> > +		err = -EINVAL;
> > +		goto out;
> > +	}
> 
> You're re-inventing the per-device QoS flag: PM_QOS_FLAG_NO_POWER_OFF
> which could be used here to prevent ->power_off from ever being called.
> 
> Alternately, if this really a static configuraion, why even register the
> ->power_off hook for these domains in the first place?

This isn't really a static configuration. The problem here is that there
is code elsewhere to deal with these domains. The CPU power partitions
for example are dealt with in the cpuidle code (or PSCI firmware). What
complicates this even further is that we have an existing custom API for
enabling/disabling power partitions (cpuidle uses that API).

Although, thinking about it some more, it seems that for the purposes of
power domains perhaps we should just not consider these power partitions
at all (i.e. not even register them).

[...]
> > @@ -831,12 +1405,19 @@ static int tegra_pmc_probe(struct platform_device *pdev)
> >  
> >  	tegra_pmc_init_tsense_reset(pmc);
> >  
> > +	if (IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)) {
> > +		err = tegra_powergate_init(pmc);
> > +		if (err < 0)
> > +			return err;
> > +	}
> 
> On many SoCs there's some special handling for the !CONFIG_PM case here
> such that all the PM domains are enabled by default in case they are not
> enabled by the bootloader.

Yeah, I think we'll need something like that for backwards-compatibility
with the old API. Converting to power domains should be done in the same
step as stubbing out the old API, and then to prevent devices using some
old DTBs to fail we'd need to enable all domains that are currently
controlled by existing drivers using the custom API.

So we don't only need this fallback for !PM_GENERIC_DOMAINS but also for
cases where we detect a DTB that's missing the nodes to describe the
domains.

Thierry
diff mbox

Patch

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index c956395cf46f..ed1ce06e3635 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -27,15 +27,20 @@ 
 #include <linux/init.h>
 #include <linux/io.h>
 #include <linux/of.h>
+#include <linux/of_platform.h>
 #include <linux/of_address.h>
 #include <linux/platform_device.h>
+#include <linux/pm_domain.h>
 #include <linux/reboot.h>
+#include <linux/regulator/consumer.h>
 #include <linux/reset.h>
+#include <linux/sched.h>
 #include <linux/seq_file.h>
 #include <linux/spinlock.h>
 
 #include <soc/tegra/common.h>
 #include <soc/tegra/fuse.h>
+#include <soc/tegra/mc.h>
 #include <soc/tegra/pmc.h>
 
 #define PMC_CNTRL			0x0
@@ -99,6 +104,30 @@ 
 
 #define GPU_RG_CNTRL			0x2d4
 
+#define MAX_CLK_NUM		5
+#define MAX_RESET_NUM		5
+#define MAX_SWGROUP_NUM		5
+
+struct tegra_powergate {
+	struct generic_pm_domain base;
+	struct tegra_pmc *pmc;
+	unsigned int id;
+	const char *name;
+	struct list_head head;
+	struct device_node *of_node;
+	struct clk *clk[MAX_CLK_NUM];
+	struct reset_control *reset[MAX_RESET_NUM];
+	struct tegra_mc_swgroup *swgroup[MAX_SWGROUP_NUM];
+	bool is_vdd;
+	struct regulator *vdd;
+};
+
+static inline struct tegra_powergate *
+to_powergate(struct generic_pm_domain *domain)
+{
+	return container_of(domain, struct tegra_powergate, base);
+}
+
 struct tegra_pmc_soc {
 	unsigned int num_powergates;
 	const char *const *powergates;
@@ -107,12 +136,15 @@  struct tegra_pmc_soc {
 
 	bool has_tsense_reset;
 	bool has_gpu_clamps;
+	bool is_legacy_powergate;
 };
 
 /**
  * struct tegra_pmc - NVIDIA Tegra PMC
+ * @dev: pointer to parent device
  * @base: pointer to I/O remapped register region
  * @clk: pointer to pclk clock
+ * @soc: SoC-specific data
  * @rate: currently configured rate of pclk
  * @suspend_mode: lowest suspend mode available
  * @cpu_good_time: CPU power good time (in microseconds)
@@ -126,7 +158,9 @@  struct tegra_pmc_soc {
  * @cpu_pwr_good_en: CPU power good signal is enabled
  * @lp0_vec_phys: physical base address of the LP0 warm boot code
  * @lp0_vec_size: size of the LP0 warm boot code
+ * @powergates: list of power gates
  * @powergates_lock: mutex for power gate register access
+ * @nb: bus notifier for generic power domains
  */
 struct tegra_pmc {
 	struct device *dev;
@@ -150,7 +184,12 @@  struct tegra_pmc {
 	u32 lp0_vec_phys;
 	u32 lp0_vec_size;
 
+	struct tegra_powergate *powergates;
 	struct mutex powergates_lock;
+	struct notifier_block nb;
+
+	struct list_head powergate_list;
+	int power_domain_num;
 };
 
 static struct tegra_pmc *pmc = &(struct tegra_pmc) {
@@ -236,6 +275,8 @@  int tegra_powergate_is_powered(int id)
 /**
  * tegra_powergate_remove_clamping() - remove power clamps for partition
  * @id: partition ID
+ *
+ * TODO: make this function static once we get rid of all outside callers
  */
 int tegra_powergate_remove_clamping(int id)
 {
@@ -256,8 +297,8 @@  int tegra_powergate_remove_clamping(int id)
 	}
 
 	/*
-	 * Tegra 2 has a bug where PCIE and VDE clamping masks are
-	 * swapped relatively to the partition ids
+	 * PCIE and VDE clamping bits are swapped relatively to the partition
+	 * ids
 	 */
 	if (id == TEGRA_POWERGATE_VDEC)
 		mask = (1 << TEGRA_POWERGATE_PCIE);
@@ -373,6 +414,8 @@  int tegra_pmc_cpu_remove_clamping(int cpuid)
 	if (id < 0)
 		return id;
 
+	usleep_range(10, 20);
+
 	return tegra_powergate_remove_clamping(id);
 }
 #endif /* CONFIG_SMP */
@@ -407,6 +450,307 @@  void tegra_pmc_restart(enum reboot_mode mode, const char *cmd)
 	tegra_pmc_writel(value, 0);
 }
 
+static bool tegra_pmc_powergate_is_powered(struct tegra_powergate *powergate)
+{
+	u32 status = tegra_pmc_readl(PWRGATE_STATUS);
+
+	if (!powergate->is_vdd)
+		return (status & BIT(powergate->id)) != 0;
+
+	if (IS_ERR(powergate->vdd))
+		return false;
+	else
+		return regulator_is_enabled(powergate->vdd);
+}
+
+static int tegra_pmc_powergate_set(struct tegra_powergate *powergate,
+				   bool new_state)
+{
+	u32 status, mask = new_state ? BIT(powergate->id) : 0;
+	bool state = false;
+	unsigned long timeout;
+	int err = -ETIMEDOUT;
+
+
+	mutex_lock(&pmc->powergates_lock);
+
+	/* check the current state of the partition */
+	status = tegra_pmc_readl(PWRGATE_STATUS);
+	state = !!(status & BIT(powergate->id));
+
+	/* nothing to do */
+	if (new_state == state) {
+		mutex_unlock(&pmc->powergates_lock);
+		return 0;
+	}
+
+	/* toggle partition state and wait for state change to finish */
+	tegra_pmc_writel(PWRGATE_TOGGLE_START | powergate->id, PWRGATE_TOGGLE);
+
+	timeout = jiffies + msecs_to_jiffies(50);
+	while (time_before(jiffies, timeout)) {
+		status = tegra_pmc_readl(PWRGATE_STATUS);
+		if ((status & BIT(powergate->id)) == mask) {
+			err = 0;
+			break;
+		}
+
+		usleep_range(10, 20);
+	}
+
+	mutex_unlock(&pmc->powergates_lock);
+
+	return err;
+}
+
+static int tegra_pmc_powergate_enable_clocks(
+		struct tegra_powergate *powergate)
+{
+	int i, err;
+
+	for (i = 0; i < MAX_CLK_NUM; i++) {
+		if (!powergate->clk[i])
+			break;
+
+		err = clk_prepare_enable(powergate->clk[i]);
+		if (err)
+			goto out;
+	}
+
+	return 0;
+
+out:
+	while (i--)
+		clk_disable_unprepare(powergate->clk[i]);
+	return err;
+}
+
+static void tegra_pmc_powergate_disable_clocks(
+		struct tegra_powergate *powergate)
+{
+	int i;
+
+	for (i = 0; i < MAX_CLK_NUM; i++) {
+		if (!powergate->clk[i])
+			break;
+
+		clk_disable_unprepare(powergate->clk[i]);
+	}
+}
+
+static int tegra_pmc_powergate_mc_flush(struct tegra_powergate *powergate)
+{
+	int i, err;
+
+	for (i = 0; i < MAX_SWGROUP_NUM; i++) {
+		if (!powergate->swgroup[i])
+			break;
+
+		err = tegra_mc_flush(powergate->swgroup[i]);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int tegra_pmc_powergate_mc_flush_done(struct tegra_powergate *powergate)
+{
+	int i, err;
+
+	for (i = 0; i < MAX_SWGROUP_NUM; i++) {
+		if (!powergate->swgroup[i])
+			break;
+
+		err = tegra_mc_flush_done(powergate->swgroup[i]);
+		if (err)
+			return err;
+	}
+
+	return 0;
+
+}
+
+static int tegra_pmc_powergate_reset_assert(
+		struct tegra_powergate *powergate)
+{
+	int i, err;
+
+	for (i = 0; i < MAX_RESET_NUM; i++) {
+		if (!powergate->reset[i])
+			break;
+
+		err = reset_control_assert(powergate->reset[i]);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int tegra_pmc_powergate_reset_deassert(
+		struct tegra_powergate *powergate)
+{
+	int i, err;
+
+	for (i = 0; i < MAX_RESET_NUM; i++) {
+		if (!powergate->reset[i])
+			break;
+
+		err = reset_control_deassert(powergate->reset[i]);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int tegra_powergate_get_regulator(struct tegra_powergate *powergate)
+{
+	struct platform_device *pdev;
+
+	if (!powergate->is_vdd)
+		return -EINVAL;
+
+	if (powergate->vdd && !IS_ERR(powergate->vdd))
+		return 0;
+
+	pdev = of_find_device_by_node(powergate->of_node);
+	if (!pdev)
+		return -EINVAL;
+
+	powergate->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
+	if (IS_ERR(powergate->vdd))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int tegra_pmc_powergate_power_on(struct generic_pm_domain *domain)
+{
+	struct tegra_powergate *powergate = to_powergate(domain);
+	struct tegra_pmc *pmc = powergate->pmc;
+	int err;
+
+	dev_dbg(pmc->dev, "> %s(domain=%p)\n", __func__, domain);
+	dev_dbg(pmc->dev, "  name: %s\n", domain->name);
+
+	if (powergate->is_vdd) {
+		err = tegra_powergate_get_regulator(powergate);
+		if (!err)
+			err = regulator_enable(powergate->vdd);
+	} else {
+		err = tegra_pmc_powergate_set(powergate, true);
+	}
+	if (err < 0)
+		goto out;
+	udelay(10);
+
+	if (pmc->soc->is_legacy_powergate) {
+		err = tegra_pmc_powergate_reset_assert(powergate);
+		if (err)
+			goto out;
+		udelay(10);
+	}
+
+	/*
+	 * Some PCIe PLLs depend on external power supplies, and the power
+	 * supplies are enabled in driver. So we don't touch PCIe clocks
+	 * here. Refer to:
+	 * Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt
+	 */
+	if (powergate->id != TEGRA_POWERGATE_PCIE) {
+		err = tegra_pmc_powergate_enable_clocks(powergate);
+		if (err)
+			goto out;
+		udelay(10);
+	}
+
+	err = tegra_powergate_remove_clamping(powergate->id);
+	if (err)
+		goto out;
+	udelay(10);
+
+	err = tegra_pmc_powergate_reset_deassert(powergate);
+	if (err)
+		goto out;
+	udelay(10);
+
+	err = tegra_pmc_powergate_mc_flush_done(powergate);
+	if (err)
+		goto out;
+	udelay(10);
+
+	if (powergate->id != TEGRA_POWERGATE_PCIE)
+		tegra_pmc_powergate_disable_clocks(powergate);
+
+	return 0;
+
+out:
+	dev_dbg(pmc->dev, "< %s() = %d\n", __func__, err);
+	return err;
+}
+
+static int tegra_pmc_powergate_power_off(struct generic_pm_domain *domain)
+{
+	struct tegra_powergate *powergate = to_powergate(domain);
+	struct tegra_pmc *pmc = powergate->pmc;
+	int err;
+
+	dev_dbg(pmc->dev, "> %s(domain=%p)\n", __func__, domain);
+	dev_dbg(pmc->dev, "  name: %s\n", domain->name);
+
+	/* never turn off these partitions */
+	switch (powergate->id) {
+	case TEGRA_POWERGATE_CPU:
+	case TEGRA_POWERGATE_CPU1:
+	case TEGRA_POWERGATE_CPU2:
+	case TEGRA_POWERGATE_CPU3:
+	case TEGRA_POWERGATE_CPU0:
+	case TEGRA_POWERGATE_C0NC:
+	case TEGRA_POWERGATE_IRAM:
+		dev_dbg(pmc->dev, "not disabling always-on partition %s\n",
+			domain->name);
+		err = -EINVAL;
+		goto out;
+	}
+
+	if (!pmc->soc->is_legacy_powergate) {
+		err = tegra_pmc_powergate_enable_clocks(powergate);
+		if (err)
+			goto out;
+		udelay(10);
+
+		err = tegra_pmc_powergate_mc_flush(powergate);
+		if (err)
+			goto out;
+		udelay(10);
+	}
+
+	err = tegra_pmc_powergate_reset_assert(powergate);
+	if (err)
+		goto out;
+	udelay(10);
+
+	if (!pmc->soc->is_legacy_powergate) {
+		tegra_pmc_powergate_disable_clocks(powergate);
+		udelay(10);
+	}
+
+	if (powergate->vdd)
+		err = regulator_disable(powergate->vdd);
+	else
+		err = tegra_pmc_powergate_set(powergate, false);
+	if (err)
+		goto out;
+
+	return 0;
+
+out:
+	dev_dbg(pmc->dev, "< %s() = %d\n", __func__, err);
+	return err;
+}
+
 static int powergate_show(struct seq_file *s, void *data)
 {
 	unsigned int i;
@@ -449,6 +793,234 @@  static int tegra_powergate_debugfs_init(void)
 	return 0;
 }
 
+static struct generic_pm_domain *
+tegra_powergate_of_xlate(struct of_phandle_args *args, void *data)
+{
+	struct tegra_pmc *pmc = data;
+	struct tegra_powergate *powergate;
+
+	dev_dbg(pmc->dev, "> %s(args=%p, data=%p)\n", __func__, args, data);
+
+	list_for_each_entry(powergate, &pmc->powergate_list, head) {
+		if (!powergate->base.name)
+			continue;
+
+		if (powergate->id == args->args[0]) {
+			dev_dbg(pmc->dev, "< %s() = %p\n", __func__, powergate);
+			return &powergate->base;
+		}
+	}
+
+	dev_dbg(pmc->dev, "< %s() = -ENOENT\n", __func__);
+	return ERR_PTR(-ENOENT);
+}
+
+static int tegra_powergate_of_get_clks(struct tegra_powergate *powergate)
+{
+	struct clk *clk;
+	int i, err;
+
+	for (i = 0; i < MAX_CLK_NUM; i++) {
+		clk = of_clk_get(powergate->of_node, i);
+		if (IS_ERR(clk)) {
+			if (PTR_ERR(clk) == -ENOENT)
+				break;
+			else
+				goto err_clks;
+		}
+
+		powergate->clk[i] = clk;
+	}
+
+	return 0;
+
+err_clks:
+	err = PTR_ERR(clk);
+	while (--i >= 0)
+		clk_put(powergate->clk[i]);
+	return err;
+}
+
+static int tegra_powergate_of_get_resets(struct tegra_powergate *powergate)
+{
+	struct reset_control *reset;
+	int i;
+
+	for (i = 0; i < MAX_RESET_NUM; i++) {
+		reset = of_reset_control_get_by_index(powergate->of_node, i);
+		if (IS_ERR(reset)) {
+			if (PTR_ERR(reset) == -ENOENT)
+				break;
+			else
+				return PTR_ERR(reset);
+		}
+
+		powergate->reset[i] = reset;
+	}
+
+	return 0;
+}
+
+static int tegra_powergate_of_get_swgroups(struct tegra_powergate *powergate)
+{
+	struct tegra_mc_swgroup *sg;
+	int i;
+
+	for (i = 0; i < MAX_SWGROUP_NUM; i++) {
+		sg = tegra_mc_find_swgroup(powergate->of_node, i);
+		if (IS_ERR_OR_NULL(sg)) {
+			if (PTR_ERR(sg) == -ENOENT)
+				break;
+			else
+				return -EINVAL;
+		}
+
+		powergate->swgroup[i] = sg;
+	}
+
+	return 0;
+}
+
+static int tegra_pmc_powergate_init_powerdomain(struct tegra_pmc *pmc)
+{
+	struct device_node *np;
+
+	for_each_compatible_node(np, NULL, "nvidia,power-domains") {
+		struct tegra_powergate *powergate;
+		const char *name;
+		int err;
+		u32 id;
+		bool off;
+
+		err = of_property_read_string(np, "name", &name);
+		if (err) {
+			dev_err(pmc->dev, "no significant name for domain\n");
+			return err;
+		}
+
+		err = of_property_read_u32(np, "domain", &id);
+		if (err) {
+			dev_err(pmc->dev, "no powergate ID for domain\n");
+			return err;
+		}
+
+		powergate = devm_kzalloc(pmc->dev, sizeof(*powergate),
+						GFP_KERNEL);
+		if (!powergate) {
+			dev_err(pmc->dev, "failed to allocate memory for domain %s\n",
+					name);
+			return -ENOMEM;
+		}
+
+		if (of_property_read_bool(np, "external-power-rail")) {
+			powergate->is_vdd = true;
+			err = tegra_powergate_get_regulator(powergate);
+			if (err) {
+				/*
+				 * The regulator might not be ready yet, so just
+				 * give a warning instead of failing the whole
+				 * init.
+				 */
+				dev_warn(pmc->dev, "couldn't locate regulator\n");
+			}
+		}
+
+		powergate->of_node = np;
+		powergate->name = name;
+		powergate->id = id;
+		powergate->base.name = kstrdup(powergate->name, GFP_KERNEL);
+		powergate->base.power_off = tegra_pmc_powergate_power_off;
+		powergate->base.power_on = tegra_pmc_powergate_power_on;
+		powergate->pmc = pmc;
+
+		err = tegra_powergate_of_get_clks(powergate);
+		if (err)
+			return err;
+
+		err = tegra_powergate_of_get_resets(powergate);
+		if (err)
+			return err;
+
+		err = tegra_powergate_of_get_swgroups(powergate);
+		if (err)
+			return err;
+
+		list_add_tail(&powergate->head, &pmc->powergate_list);
+
+		if ((powergate->is_vdd && !IS_ERR(powergate->vdd)) ||
+			!powergate->is_vdd)
+			tegra_pmc_powergate_power_off(&powergate->base);
+
+		off = !tegra_pmc_powergate_is_powered(powergate);
+		pm_genpd_init(&powergate->base, NULL, off);
+
+		pmc->power_domain_num++;
+
+		dev_info(pmc->dev, "added power domain %s\n", powergate->name);
+	}
+
+	dev_info(pmc->dev, "%d power domains added\n", pmc->power_domain_num);
+	return 0;
+}
+
+static int tegra_pmc_powergate_init_subdomain(struct tegra_pmc *pmc)
+{
+	struct tegra_powergate *powergate;
+
+	list_for_each_entry(powergate, &pmc->powergate_list, head) {
+		struct device_node *pdn;
+		struct tegra_powergate *parent = NULL;
+		struct tegra_powergate *temp;
+		int err;
+
+		pdn = of_parse_phandle(powergate->of_node, "depend-on", 0);
+		if (!pdn)
+			continue;
+
+		list_for_each_entry(temp, &pmc->powergate_list, head) {
+			if (temp->of_node == pdn) {
+				parent = temp;
+				break;
+			}
+		}
+
+		if (!parent)
+			return -EINVAL;
+
+		err = pm_genpd_add_subdomain_names(parent->name,
+				powergate->name);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int tegra_powergate_init(struct tegra_pmc *pmc)
+{
+	struct device_node *np = pmc->dev->of_node;
+	int err = 0;
+
+	dev_dbg(pmc->dev, "> %s(pmc=%p)\n", __func__, pmc);
+
+	INIT_LIST_HEAD(&pmc->powergate_list);
+	err = tegra_pmc_powergate_init_powerdomain(pmc);
+	if (err)
+		goto out;
+
+	err = tegra_pmc_powergate_init_subdomain(pmc);
+	if (err < 0)
+		return err;
+
+	err = __of_genpd_add_provider(np, tegra_powergate_of_xlate, pmc);
+	if (err < 0)
+		return err;
+
+out:
+	dev_dbg(pmc->dev, "< %s() = %d\n", __func__, err);
+	return err;
+}
+
 static int tegra_io_rail_prepare(int id, unsigned long *request,
 				 unsigned long *status, unsigned int *bit)
 {
@@ -806,6 +1378,8 @@  static int tegra_pmc_probe(struct platform_device *pdev)
 	struct resource *res;
 	int err;
 
+	dev_dbg(&pdev->dev, "> %s(pdev=%p)\n", __func__, pdev);
+
 	err = tegra_pmc_parse_dt(pmc, pdev->dev.of_node);
 	if (err < 0)
 		return err;
@@ -831,12 +1405,19 @@  static int tegra_pmc_probe(struct platform_device *pdev)
 
 	tegra_pmc_init_tsense_reset(pmc);
 
+	if (IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)) {
+		err = tegra_powergate_init(pmc);
+		if (err < 0)
+			return err;
+	}
+
 	if (IS_ENABLED(CONFIG_DEBUG_FS)) {
 		err = tegra_powergate_debugfs_init();
 		if (err < 0)
 			return err;
 	}
 
+	dev_dbg(&pdev->dev, "< %s()\n", __func__);
 	return 0;
 }
 
@@ -876,6 +1457,7 @@  static const struct tegra_pmc_soc tegra20_pmc_soc = {
 	.cpu_powergates = NULL,
 	.has_tsense_reset = false,
 	.has_gpu_clamps = false,
+	.is_legacy_powergate = true,
 };
 
 static const char * const tegra30_powergates[] = {
@@ -909,6 +1491,7 @@  static const struct tegra_pmc_soc tegra30_pmc_soc = {
 	.cpu_powergates = tegra30_cpu_powergates,
 	.has_tsense_reset = true,
 	.has_gpu_clamps = false,
+	.is_legacy_powergate = true,
 };
 
 static const char * const tegra114_powergates[] = {
@@ -946,6 +1529,7 @@  static const struct tegra_pmc_soc tegra114_pmc_soc = {
 	.cpu_powergates = tegra114_cpu_powergates,
 	.has_tsense_reset = true,
 	.has_gpu_clamps = false,
+	.is_legacy_powergate = false,
 };
 
 static const char * const tegra124_powergates[] = {
@@ -989,6 +1573,7 @@  static const struct tegra_pmc_soc tegra124_pmc_soc = {
 	.cpu_powergates = tegra124_cpu_powergates,
 	.has_tsense_reset = true,
 	.has_gpu_clamps = true,
+	.is_legacy_powergate = false,
 };
 
 static const struct of_device_id tegra_pmc_match[] = {
diff --git a/include/dt-bindings/power/tegra-powergate.h b/include/dt-bindings/power/tegra-powergate.h
new file mode 100644
index 000000000000..b8265167c20e
--- /dev/null
+++ b/include/dt-bindings/power/tegra-powergate.h
@@ -0,0 +1,30 @@ 
+#ifndef _DT_BINDINGS_POWER_TEGRA_POWERGATE_H
+#define _DT_BINDINGS_POWER_TEGRA_POWERGATE_H
+
+#define TEGRA_POWERGATE_CPU	0
+#define TEGRA_POWERGATE_3D	1
+#define TEGRA_POWERGATE_VENC	2
+#define TEGRA_POWERGATE_PCIE	3
+#define TEGRA_POWERGATE_VDEC	4
+#define TEGRA_POWERGATE_L2	5
+#define TEGRA_POWERGATE_MPE	6
+#define TEGRA_POWERGATE_HEG	7
+#define TEGRA_POWERGATE_SATA	8
+#define TEGRA_POWERGATE_CPU1	9
+#define TEGRA_POWERGATE_CPU2	10
+#define TEGRA_POWERGATE_CPU3	11
+#define TEGRA_POWERGATE_CELP	12
+#define TEGRA_POWERGATE_3D1	13
+#define TEGRA_POWERGATE_CPU0	14
+#define TEGRA_POWERGATE_C0NC	15
+#define TEGRA_POWERGATE_C1NC	16
+#define TEGRA_POWERGATE_SOR	17
+#define TEGRA_POWERGATE_DIS	18
+#define TEGRA_POWERGATE_DISB	19
+#define TEGRA_POWERGATE_XUSBA	20
+#define TEGRA_POWERGATE_XUSBB	21
+#define TEGRA_POWERGATE_XUSBC	22
+#define TEGRA_POWERGATE_VIC	23
+#define TEGRA_POWERGATE_IRAM	24
+
+#endif