Adds generic PM support to the PMC driver where the PM domains are
populated from device-tree and the PM domain consumer devices are
bound to their relevant PM domains via device-tree as well.

Update the tegra_powergate_sequence_power_up() API so that internally
it calls the same tegra_powergate_xxx functions that are used by the
tegra generic power domain code for consistency.

This is based upon work by Thierry Reding <tred...@nvidia.com>
and Vince Hsu <vin...@nvidia.com>.

Signed-off-by: Jon Hunter <jonath...@nvidia.com>
---
 drivers/soc/tegra/pmc.c                     | 504 ++++++++++++++++++++++++++--
 include/dt-bindings/power/tegra-powergate.h |  35 ++
 include/soc/tegra/pmc.h                     |  38 +--
 3 files changed, 513 insertions(+), 64 deletions(-)
 create mode 100644 include/dt-bindings/power/tegra-powergate.h

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index b412098b8197..76bee999c85b 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -19,6 +19,8 @@
 
 #define pr_fmt(fmt) "tegra-pmc: " fmt
 
+#include <dt-bindings/power/tegra-powergate.h>
+
 #include <linux/kernel.h>
 #include <linux/clk.h>
 #include <linux/clk/tegra.h>
@@ -31,7 +33,9 @@
 #include <linux/iopoll.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
+#include <linux/of_platform.h>
 #include <linux/platform_device.h>
+#include <linux/pm_domain.h>
 #include <linux/reboot.h>
 #include <linux/reset.h>
 #include <linux/seq_file.h>
@@ -105,6 +109,20 @@
 #define PMC_PWRGATE_STATE(status, id)  ((status & BIT(id)) != 0)
 #define PMC_PWRGATE_IS_VALID(id)       (pmc->powergates_mask & BIT(id))
 
+struct tegra_powergate {
+       struct generic_pm_domain genpd;
+       struct generic_pm_domain *parent;
+       struct tegra_pmc *pmc;
+       unsigned int id;
+       struct list_head node;
+       struct device_node *of_node;
+       struct clk **clks;
+       unsigned int num_clks;
+       struct reset_control **resets;
+       unsigned int num_resets;
+       bool disable_clocks;
+};
+
 struct tegra_pmc_soc {
        unsigned int num_powergates;
        const char *const *powergates;
@@ -137,6 +155,7 @@ struct tegra_pmc_soc {
  * @lp0_vec_phys: physical base address of the LP0 warm boot code
  * @lp0_vec_size: size of the LP0 warm boot code
  * @powergates_mask: Bit mask of valid power gates
+ * @powergates_list: list of power gates
  * @powergates_lock: mutex for power gate register access
  */
 struct tegra_pmc {
@@ -163,6 +182,7 @@ struct tegra_pmc {
        u32 lp0_vec_size;
        u32 powergates_mask;
 
+       struct list_head powergates_list;
        struct mutex powergates_lock;
 };
 
@@ -171,6 +191,12 @@ static struct tegra_pmc *pmc = &(struct tegra_pmc) {
        .suspend_mode = TEGRA_SUSPEND_NONE,
 };
 
+static inline struct tegra_powergate *
+to_powergate(struct generic_pm_domain *domain)
+{
+       return container_of(domain, struct tegra_powergate, genpd);
+}
+
 static u32 tegra_pmc_readl(unsigned long offset)
 {
        return readl(pmc->base + offset);
@@ -214,6 +240,177 @@ static int tegra_powergate_set(int id, bool new_state)
        return err;
 }
 
+static void tegra_powergate_disable_clocks(struct tegra_powergate *pg)
+{
+       unsigned int i;
+
+       for (i = 0; i < pg->num_clks; i++)
+               clk_disable_unprepare(pg->clks[i]);
+}
+
+static int tegra_powergate_enable_clocks(struct tegra_powergate *pg)
+{
+       unsigned int i;
+       int err;
+
+       for (i = 0; i < pg->num_clks; i++) {
+               err = clk_prepare_enable(pg->clks[i]);
+               if (err)
+                       goto out;
+       }
+
+       return 0;
+
+out:
+       while (i--)
+               clk_disable_unprepare(pg->clks[i]);
+
+       return err;
+}
+
+static int tegra_powergate_reset_assert(struct tegra_powergate *pg)
+{
+       unsigned int i;
+       int err;
+
+       for (i = 0; i < pg->num_resets; i++) {
+               err = reset_control_assert(pg->resets[i]);
+               if (err)
+                       return err;
+       }
+
+       return 0;
+}
+
+static int tegra_powergate_reset_deassert(struct tegra_powergate *pg)
+{
+       unsigned int i;
+       int err;
+
+       for (i = 0; i < pg->num_resets; i++) {
+               err = reset_control_deassert(pg->resets[i]);
+               if (err)
+                       return err;
+       }
+
+       return 0;
+}
+
+static int tegra_powergate_power_up(struct tegra_powergate *pg,
+                                   bool disable_clocks)
+{
+       int err;
+
+       err = tegra_powergate_reset_assert(pg);
+       if (err)
+               return err;
+
+       usleep_range(10, 20);
+
+       err = tegra_powergate_set(pg->id, true);
+       if (err < 0)
+               return err;
+
+       usleep_range(10, 20);
+
+       err = tegra_powergate_enable_clocks(pg);
+       if (err)
+               goto err_clks;
+
+       usleep_range(10, 20);
+
+       tegra_powergate_remove_clamping(pg->id);
+
+       usleep_range(10, 20);
+
+       err = tegra_powergate_reset_deassert(pg);
+       if (err)
+               goto err_reset;
+
+       usleep_range(10, 20);
+
+       if (disable_clocks)
+               tegra_powergate_disable_clocks(pg);
+
+       return 0;
+
+err_reset:
+       tegra_powergate_disable_clocks(pg);
+       usleep_range(10, 20);
+err_clks:
+       tegra_powergate_set(pg->id, false);
+
+       return err;
+}
+
+static int tegra_powergate_power_down(struct tegra_powergate *pg,
+                                     bool enable_clocks)
+{
+       int err;
+
+       if (enable_clocks) {
+               err = tegra_powergate_enable_clocks(pg);
+               if (err)
+                       return err;
+
+               usleep_range(10, 20);
+       }
+
+       err = tegra_powergate_reset_assert(pg);
+       if (err)
+               goto err_reset;
+
+       usleep_range(10, 20);
+
+       tegra_powergate_disable_clocks(pg);
+
+       usleep_range(10, 20);
+
+       err = tegra_powergate_set(pg->id, false);
+       if (err)
+               goto err_powergate;
+
+       return 0;
+
+err_powergate:
+       tegra_powergate_enable_clocks(pg);
+       usleep_range(10, 20);
+       tegra_powergate_reset_deassert(pg);
+       usleep_range(10, 20);
+err_reset:
+       tegra_powergate_disable_clocks(pg);
+
+       return err;
+}
+
+static int tegra_genpd_power_on(struct generic_pm_domain *domain)
+{
+       struct tegra_powergate *pg = to_powergate(domain);
+       struct tegra_pmc *pmc = pg->pmc;
+       int err;
+
+       err = tegra_powergate_power_up(pg, pg->disable_clocks);
+       if (err)
+               dev_err(pmc->dev, "failed to turn on PM domain %s: %d\n",
+                       pg->of_node->name, err);
+
+       return err;
+}
+
+static int tegra_genpd_power_off(struct generic_pm_domain *domain)
+{
+       struct tegra_powergate *pg = to_powergate(domain);
+       struct tegra_pmc *pmc = pg->pmc;
+       int err;
+
+       err = tegra_powergate_power_down(pg, !pg->disable_clocks);
+       if (err)
+               dev_err(pmc->dev, "failed to turn off PM domain %s: %d\n",
+                       pg->of_node->name, err);
+
+       return err;
+}
+
 /**
  * tegra_powergate_power_on() - power on partition
  * @id: partition ID
@@ -308,35 +505,20 @@ EXPORT_SYMBOL(tegra_powergate_remove_clamping);
 int tegra_powergate_sequence_power_up(int id, struct clk *clk,
                                      struct reset_control *rst)
 {
-       int ret;
-
-       reset_control_assert(rst);
-
-       ret = tegra_powergate_power_on(id);
-       if (ret)
-               goto err_power;
-
-       ret = clk_prepare_enable(clk);
-       if (ret)
-               goto err_clk;
-
-       usleep_range(10, 20);
-
-       ret = tegra_powergate_remove_clamping(id);
-       if (ret)
-               goto err_clamp;
+       struct tegra_powergate pg;
+       int err;
 
-       usleep_range(10, 20);
-       reset_control_deassert(rst);
+       pg.id = id;
+       pg.clks = &clk;
+       pg.num_clks = 1;
+       pg.resets = &rst;
+       pg.num_resets = 1;
 
-       return 0;
+       err = tegra_powergate_power_up(&pg, false);
+       if (err)
+               pr_err("failed to turn on partition %d: %d\n", id, err);
 
-err_clamp:
-       clk_disable_unprepare(clk);
-err_clk:
-       tegra_powergate_power_off(id);
-err_power:
-       return ret;
+       return err;
 }
 EXPORT_SYMBOL(tegra_powergate_sequence_power_up);
 
@@ -476,6 +658,260 @@ static int tegra_powergate_debugfs_init(void)
        return 0;
 }
 
+static int tegra_powergate_of_get_clks(struct device *dev,
+                                      struct tegra_powergate *pg)
+{
+       struct clk *clk;
+       unsigned int i, count;
+       int err;
+
+       /*
+        * Determine number of clocks used by the powergate
+        */
+       for (count = 0; ; count++) {
+               clk = of_clk_get(pg->of_node, count);
+               if (IS_ERR(clk))
+                       break;
+
+               clk_put(clk);
+       }
+
+       if (PTR_ERR(clk) != -ENOENT)
+               return PTR_ERR(clk);
+
+       if (count == 0)
+               return -ENODEV;
+
+       pg->clks = devm_kcalloc(dev, count, sizeof(clk), GFP_KERNEL);
+       if (!pg->clks)
+               return -ENOMEM;
+
+       for (i = 0; i < count; i++) {
+               pg->clks[i] = of_clk_get(pg->of_node, i);
+               if (IS_ERR(pg->clks[i])) {
+                       err = PTR_ERR(pg->clks[i]);
+                       goto err;
+               }
+       }
+
+       pg->num_clks = count;
+
+       return 0;
+
+err:
+       while (i--)
+               clk_put(pg->clks[i]);
+
+       return err;
+}
+
+static int tegra_powergate_of_get_resets(struct device *dev,
+                                        struct tegra_powergate *pg)
+{
+       struct reset_control *rst;
+       unsigned int i, count;
+       int err;
+
+       /*
+        * Determine number of resets used by the powergate
+        */
+       for (count = 0; ; count++) {
+               rst = of_reset_control_get_by_index(pg->of_node, count);
+               if (IS_ERR(rst))
+                       break;
+
+               reset_control_put(rst);
+       }
+
+       if (PTR_ERR(rst) != -ENOENT)
+               return PTR_ERR(rst);
+
+       if (count == 0)
+               return -ENODEV;
+
+       pg->resets = devm_kcalloc(dev, count, sizeof(rst), GFP_KERNEL);
+       if (!pg->resets)
+               return -ENOMEM;
+
+       for (i = 0; i < count; i++) {
+               pg->resets[i] = of_reset_control_get_by_index(pg->of_node, i);
+               if (IS_ERR(pg->resets[i])) {
+                       err = PTR_ERR(pg->resets[i]);
+                       goto err;
+               }
+       }
+
+       pg->num_resets = count;
+
+       return 0;
+
+err:
+       while (i--)
+               reset_control_put(pg->resets[i]);
+
+       return err;
+}
+
+static struct tegra_powergate *
+tegra_powergate_add_one(struct tegra_pmc *pmc, struct device_node *np,
+                       struct generic_pm_domain *parent)
+{
+       struct tegra_powergate *pg;
+       unsigned int id;
+       bool off;
+       int err;
+
+       /*
+        * If the powergate ID is missing or invalid then return NULL
+        * to skip this one and allow any others to be created.
+        */
+       err = of_property_read_u32(np, "nvidia,powergate", &id);
+       if (err) {
+               dev_WARN(pmc->dev, "no powergate ID for node %s\n", np->name);
+               return NULL;
+       }
+
+       if (!PMC_PWRGATE_IS_VALID(id)) {
+               dev_WARN(pmc->dev, "powergate ID invalid for %s\n", np->name);
+               return NULL;
+       }
+
+       pg = devm_kzalloc(pmc->dev, sizeof(*pg), GFP_KERNEL);
+       if (!pg) {
+               err = -ENOMEM;
+               goto err_mem;
+       }
+
+       pg->id = id;
+       pg->of_node = np;
+       pg->parent = parent;
+       pg->genpd.name = np->name;
+       pg->genpd.power_off = tegra_genpd_power_off;
+       pg->genpd.power_on = tegra_genpd_power_on;
+       pg->pmc = pmc;
+
+       pg->disable_clocks = of_property_read_bool(np,
+                               "nvidia,powergate-disable-clocks");
+
+       err = tegra_powergate_of_get_clks(pmc->dev, pg);
+       if (err)
+               goto err_mem;
+
+       err = tegra_powergate_of_get_resets(pmc->dev, pg);
+       if (err)
+               goto err_reset;
+
+       off = !tegra_powergate_is_powered(pg->id);
+
+       pm_genpd_init(&pg->genpd, NULL, off);
+
+       if (pg->parent) {
+               err = pm_genpd_add_subdomain(pg->parent, &pg->genpd);
+               if (err)
+                       goto err_subdomain;
+       }
+
+       err = of_genpd_add_provider_simple(pg->of_node, &pg->genpd);
+       if (err)
+               goto err_provider;
+
+       list_add_tail(&pg->node, &pmc->powergates_list);
+
+       dev_info(pmc->dev, "added power domain %s\n", pg->genpd.name);
+
+       return pg;
+
+err_provider:
+       WARN_ON(pm_genpd_remove_subdomain(pg->parent, &pg->genpd));
+err_subdomain:
+       WARN_ON(pm_genpd_remove(&pg->genpd));
+       while (pg->num_resets--)
+               reset_control_put(pg->resets[pg->num_resets]);
+err_reset:
+       while (pg->num_clks--)
+               clk_put(pg->clks[pg->num_clks]);
+err_mem:
+       dev_err(pmc->dev, "failed to create power domain for node %s\n",
+               np->name);
+
+       return ERR_PTR(err);
+}
+
+static int tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np,
+                              struct generic_pm_domain *parent)
+{
+       struct tegra_powergate *pg;
+       struct device_node *child;
+       int err = 0;
+
+       for_each_child_of_node(np, child) {
+               if (err)
+                       goto err;
+
+               pg = tegra_powergate_add_one(pmc, child, parent);
+               if (IS_ERR(pg)) {
+                       err = PTR_ERR(pg);
+                       goto err;
+               }
+
+               if (pg)
+                       err = tegra_powergate_add(pmc, child, pg->parent);
+
+err:
+               of_node_put(child);
+
+               if (err)
+                       return err;
+       }
+
+       return err;
+}
+
+static void tegra_powergate_remove(struct tegra_pmc *pmc)
+{
+       struct tegra_powergate *pg, *n;
+
+       list_for_each_entry_safe(pg, n, &pmc->powergates_list, node) {
+               of_genpd_del_provider(pg->of_node);
+               if (pg->parent)
+                       pm_genpd_remove_subdomain(pg->parent, &pg->genpd);
+               pm_genpd_remove(&pg->genpd);
+
+               while (pg->num_clks--)
+                       clk_put(pg->clks[pg->num_clks]);
+
+               while (pg->num_resets--)
+                       reset_control_put(pg->resets[pg->num_resets]);
+
+               list_del(&pg->node);
+       }
+}
+
+static int tegra_powergate_init(struct tegra_pmc *pmc)
+{
+       struct device_node *np;
+       int err;
+
+       INIT_LIST_HEAD(&pmc->powergates_list);
+
+       np = of_get_child_by_name(pmc->dev->of_node, "pm-domains");
+       if (!np) {
+               dev_dbg(pmc->dev, "pm-domains node not found\n");
+               return 0;
+       }
+
+       err = tegra_powergate_add(pmc, np, NULL);
+       if (err) {
+               tegra_powergate_remove(pmc);
+               of_node_put(np);
+               return err;
+       }
+
+       of_node_put(np);
+
+       return 0;
+}
+
 static int tegra_io_rail_prepare(int id, unsigned long *request,
                                 unsigned long *status, unsigned int *bit)
 {
@@ -850,21 +1286,31 @@ static int tegra_pmc_probe(struct platform_device *pdev)
 
        tegra_pmc_init_tsense_reset(pmc);
 
+       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;
+                       goto err_debugfs;
        }
 
        err = register_restart_handler(&tegra_pmc_restart_handler);
        if (err) {
-               debugfs_remove(pmc->debugfs);
                dev_err(&pdev->dev, "unable to register restart handler, %d\n",
                        err);
-               return err;
+               goto err_restart;
        }
 
        return 0;
+
+err_restart:
+       debugfs_remove(pmc->debugfs);
+err_debugfs:
+       tegra_powergate_remove(pmc);
+
+       return err;
 }
 
 #if defined(CONFIG_PM_SLEEP) && defined(CONFIG_ARM)
diff --git a/include/dt-bindings/power/tegra-powergate.h 
b/include/dt-bindings/power/tegra-powergate.h
new file mode 100644
index 000000000000..12b72aa77d6a
--- /dev/null
+++ b/include/dt-bindings/power/tegra-powergate.h
@@ -0,0 +1,35 @@
+#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
+#define TEGRA_POWERGATE_NVDEC  25
+#define TEGRA_POWERGATE_NVJPG  26
+#define TEGRA_POWERGATE_AUD    27
+#define TEGRA_POWERGATE_DFD    28
+#define TEGRA_POWERGATE_VE2    29
+
+#endif
diff --git a/include/soc/tegra/pmc.h b/include/soc/tegra/pmc.h
index d18efe402ff1..61c9f847f2b2 100644
--- a/include/soc/tegra/pmc.h
+++ b/include/soc/tegra/pmc.h
@@ -19,6 +19,8 @@
 #ifndef __SOC_TEGRA_PMC_H__
 #define __SOC_TEGRA_PMC_H__
 
+#include <dt-bindings/power/tegra-powergate.h>
+
 #include <linux/reboot.h>
 
 #include <soc/tegra/pm.h>
@@ -39,42 +41,8 @@ int tegra_pmc_cpu_remove_clamping(int cpuid);
 #endif /* CONFIG_SMP */
 
 /*
- * powergate and I/O rail APIs
+ * I/O rail APIs
  */
-
-#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
-#define TEGRA_POWERGATE_NVDEC  25
-#define TEGRA_POWERGATE_NVJPG  26
-#define TEGRA_POWERGATE_AUD    27
-#define TEGRA_POWERGATE_DFD    28
-#define TEGRA_POWERGATE_VE2    29
-
-#define TEGRA_POWERGATE_3D0    TEGRA_POWERGATE_3D
-
 #define TEGRA_IO_RAIL_CSIA     0
 #define TEGRA_IO_RAIL_CSIB     1
 #define TEGRA_IO_RAIL_DSI      2
-- 
2.1.4

--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to