ARM: tegra: power: Refactored kernel powergate code
Karan Jhavar [Tue, 17 May 2011 00:00:43 +0000 (17:00 -0700)]
This change provides a centralized location for powergating modules.
It would take care of switching on/off clocks while un-powergating/
powergating modules respectively.

Bug: 814267
Original-Change-Id: Ic80dc517f634c29085c8e089bdaa32c6fd742710
Reviewed-on: http://git-master/r/31776
Reviewed-by: Niket Sirsi <nsirsi@nvidia.com>
Tested-by: Niket Sirsi <nsirsi@nvidia.com>

Rebase-Id: Re0c233ed4bacc27feb7b210cddc6ff3e487c528f

arch/arm/mach-tegra/clock.c
arch/arm/mach-tegra/common.c
arch/arm/mach-tegra/include/mach/powergate.h
arch/arm/mach-tegra/pcie.c
arch/arm/mach-tegra/platsmp.c
arch/arm/mach-tegra/powergate.c
include/linux/clk/tegra.h

index 7df5c54..7f8895c 100644 (file)
@@ -655,6 +655,12 @@ out:
        return ret;
 }
 
+int tegra_is_clk_enabled(struct clk *c)
+{
+       return c->refcnt;
+}
+EXPORT_SYMBOL(tegra_is_clk_enabled);
+
 /* dvfs initialization may lower default maximum rate */
 void __init tegra_init_max_rate(struct clk *c, unsigned long max_rate)
 {
index 52f45f8..9d187f3 100644 (file)
@@ -176,14 +176,14 @@ void tegra_init_cache(u32 tag_latency, u32 data_latency)
 
 static void __init tegra_init_power(void)
 {
-       tegra_powergate_power_off(TEGRA_POWERGATE_MPE);
-       tegra_powergate_power_off(TEGRA_POWERGATE_3D);
+       tegra_powergate_partition_with_clk_off(TEGRA_POWERGATE_MPE);
+       tegra_powergate_partition_with_clk_off(TEGRA_POWERGATE_3D);
+#ifndef CONFIG_ARCH_TEGRA_2x_SOC
+       tegra_powergate_partition_with_clk_off(TEGRA_POWERGATE_3D1);
+#endif
 #ifdef CONFIG_ARCH_TEGRA_2x_SOC
        /* for TEGRA_3x_SOC it will be handled seperately */
-       tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
-#endif
-#ifndef CONFIG_ARCH_TEGRA_2x_SOC
-       tegra_powergate_power_off(TEGRA_POWERGATE_3D1);
+       tegra_powergate_partition_with_clk_off(TEGRA_POWERGATE_PCIE);
 #endif
 }
 
index 968b17e..b5a2de6 100644 (file)
@@ -44,12 +44,39 @@ int  __init tegra_powergate_init(void);
 
 int tegra_cpu_powergate_id(int cpuid);
 int tegra_powergate_is_powered(int id);
-int tegra_powergate_power_on(int id);
-int tegra_powergate_power_off(int id);
 int tegra_powergate_remove_clamping(int id);
-const char* tegra_powergate_get_name(int id);
+const char *tegra_powergate_get_name(int id);
 
-/* Must be called with clk disabled, and returns with clk enabled */
-int tegra_powergate_sequence_power_up(int id, struct clk *clk);
+/*
+ * Functions to powergate/un-powergate partitions.
+ * Handle clk management in the API's.
+ *
+ * tegra_powergate_partition_with_clk_off() can be called with
+ * clks ON. It disables all required clks.
+ *
+ * tegra_unpowergate_partition_with_clk_on() can be called with
+ * all required clks OFF. Returns with all clks ON.
+ *
+ * Warning: In general drivers should take care of the module
+ * clks and use tegra_powergate_partition() &
+ * tegra_unpowergate_partition() API's.
+ */
+int tegra_powergate_partition_with_clk_off(int id);
+int tegra_unpowergate_partition_with_clk_on(int id);
+
+/*
+ * Functions to powergate un-powergate partitions.
+ * Drivers are responsible for clk enable-disable
+ *
+ * tegra_powergate_partition() should be called with all
+ * required clks OFF. Drivers should disable clks BEFORE
+ * calling this fucntion
+ *
+ * tegra_unpowergate_partition should be called with all
+ * required clks OFF. Returns with all clks OFF. Drivers
+ * should enable all clks AFTER this function
+ */
+int tegra_powergate_partition(int id);
+int tegra_unpowergate_partition(int id);
 
 #endif /* _MACH_TEGRA_POWERGATE_H_ */
index ecb4f8a..26697d2 100644 (file)
@@ -655,7 +655,7 @@ static void tegra_pcie_power_off(void)
        tegra_periph_reset_assert(tegra_pcie.afi_clk);
        tegra_periph_reset_assert(tegra_pcie.pex_clk);
 
-       tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
+       tegra_powergate_partition(TEGRA_POWERGATE_PCIE);
        tegra_pcie_xclk_clamp(true);
 }
 
@@ -670,8 +670,7 @@ static int tegra_pcie_power_regate(void)
        tegra_periph_reset_assert(tegra_pcie.pcie_xclk);
        tegra_periph_reset_assert(tegra_pcie.afi_clk);
 
-       err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_PCIE,
-                                               tegra_pcie.pex_clk);
+       err = tegra_unpowergate_partition(TEGRA_POWERGATE_PCIE);
        if (err) {
                pr_err("PCIE: powerup sequence failed: %d\n", err);
                return err;
index d7f99ed..a379f7e 100644 (file)
@@ -142,7 +142,7 @@ static int tegra30_power_up_cpu(unsigned int cpu)
 
        /* If this is the first boot, toggle powergates directly. */
        if (!tegra_powergate_is_powered(pwrgateid)) {
-               ret = tegra_powergate_power_on(pwrgateid);
+               ret = tegra_unpowergate_partition(pwrgateid);
                if (ret)
                        return ret;
 
index 15d330f..0c5760c 100644 (file)
 
 #include <mach/powergate.h>
 
+#include "clock.h"
 #include "fuse.h"
 #include "iomap.h"
 
 #define PWRGATE_TOGGLE         0x30
-#define  PWRGATE_TOGGLE_START  (1 << 8)
+#define PWRGATE_TOGGLE_START   (1 << 8)
 
 #define REMOVE_CLAMPING                0x34
 
@@ -53,7 +54,7 @@ static u8 tegra30_cpu_domains[] = {
 #define MC_CLIENT_HOTRESET_CTRL        0x200
 #define MC_CLIENT_HOTRESET_STAT        0x204
 
-typedef enum {
+enum mc_client {
        MC_CLIENT_AFI   = 0,
        MC_CLIENT_AVPC  = 1,
        MC_CLIENT_DC    = 2,
@@ -73,41 +74,63 @@ typedef enum {
        MC_CLIENT_VDE   = 16,
        MC_CLIENT_VI    = 17,
        MC_CLIENT_LAST  = -1,
-} MC_CLIENT;
+};
+
+#define MAX_CLK_EN_NUM                 4
 
 static DEFINE_SPINLOCK(tegra_powergate_lock);
 
 #define MAX_HOTRESET_CLIENT_NUM                3
 
-typedef struct {
-       const char * name;
-       MC_CLIENT hot_reset_clients[MAX_HOTRESET_CLIENT_NUM];
-       /* add clocks for each partition*/
-} powergate_partition;
+struct partition_clk_info {
+       const char *clk_name;
+       bool only_reset;
+       /* true if clk is only used in assert/deassert reset and not while enable-den*/
+       struct clk *clk_ptr;
+};
+
+struct powergate_partition {
+       const char *name;
+       enum mc_client hot_reset_clients[MAX_HOTRESET_CLIENT_NUM];
+       struct partition_clk_info clk_info[MAX_CLK_EN_NUM];
+};
 
-static powergate_partition powergate_partition_info[] = {
-       [TEGRA_POWERGATE_CPU]   = { "cpu0",     {MC_CLIENT_LAST} },
-       [TEGRA_POWERGATE_L2]    = { "l2",       {MC_CLIENT_LAST} },
+static struct powergate_partition powergate_partition_info[] = {
+       [TEGRA_POWERGATE_CPU]   = { "cpu0",     {MC_CLIENT_LAST}, },
+       [TEGRA_POWERGATE_L2]    = { "l2",       {MC_CLIENT_LAST}, },
        [TEGRA_POWERGATE_3D]    = { "3d0",
-                                               {MC_CLIENT_NV, MC_CLIENT_LAST} },
+                                               {MC_CLIENT_NV, MC_CLIENT_LAST},
+                                               {{"3d", false} }, },
        [TEGRA_POWERGATE_PCIE]  = { "pcie",
-                                               {MC_CLIENT_AFI, MC_CLIENT_LAST} },
+                                               {MC_CLIENT_AFI, MC_CLIENT_LAST},
+                                               {{"afi", false},
+                                               {"pcie", false},
+                                               {"pciex", true} }, },
        [TEGRA_POWERGATE_VDEC]  = { "vde",
-                                               {MC_CLIENT_VDE, MC_CLIENT_LAST} },
+                                               {MC_CLIENT_VDE, MC_CLIENT_LAST},
+                                               {{"vde", false} }, },
        [TEGRA_POWERGATE_MPE]   = { "mpe",
-                                               {MC_CLIENT_MPE, MC_CLIENT_LAST} },
+                                               {MC_CLIENT_MPE, MC_CLIENT_LAST},
+                                               {{"mpe", false} }, },
        [TEGRA_POWERGATE_VENC]  = { "ve",
-                                               {MC_CLIENT_ISP, MC_CLIENT_VI, MC_CLIENT_LAST} },
-       [TEGRA_POWERGATE_CPU1]  = { "cpu1",     {MC_CLIENT_LAST}},
-       [TEGRA_POWERGATE_CPU2]  = { "cpu2",     {MC_CLIENT_LAST}},
-       [TEGRA_POWERGATE_CPU3]  = { "cpu3",     {MC_CLIENT_LAST}},
-       [TEGRA_POWERGATE_A9LP]  = { "a9lp",     {MC_CLIENT_LAST}},
-       [TEGRA_POWERGATE_SATA]  = { "sata",
-                                               {MC_CLIENT_SATA, MC_CLIENT_LAST} },
+                                               {MC_CLIENT_ISP, MC_CLIENT_VI, MC_CLIENT_LAST},
+                                               {{"isp", false}, {"vi", false},
+                                               {"csi", false} }, },
+       [TEGRA_POWERGATE_CPU1]  = { "cpu1",     {MC_CLIENT_LAST}, },
+       [TEGRA_POWERGATE_CPU2]  = { "cpu2",     {MC_CLIENT_LAST}, },
+       [TEGRA_POWERGATE_CPU3]  = { "cpu3",     {MC_CLIENT_LAST}, },
+       [TEGRA_POWERGATE_A9LP]  = { "a9lp",     {MC_CLIENT_LAST}, },
+       [TEGRA_POWERGATE_SATA]  = { "sata",     {MC_CLIENT_SATA, MC_CLIENT_LAST},
+                                               {{"sata", false},
+                                               {"sata_cold", true} }, },
        [TEGRA_POWERGATE_3D1]   = { "3d1",
-                                               {MC_CLIENT_NV2, MC_CLIENT_LAST} },
+                                               {MC_CLIENT_NV2, MC_CLIENT_LAST},
+                                               {{"3d2", false} }, },
        [TEGRA_POWERGATE_HEG]   = { "heg",
-                                               {MC_CLIENT_G2, MC_CLIENT_EPP, MC_CLIENT_HC} },
+                                               {MC_CLIENT_G2, MC_CLIENT_EPP, MC_CLIENT_HC},
+                                               {{"2d", false}, {"epp", false},
+                                               {"host1x", false},
+                                               {"3d", true} }, },
 };
 
 static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
@@ -138,7 +161,7 @@ static void mc_write(u32 val, unsigned long reg)
 static void mc_flush(int id)
 {
        u32 idx, rst_ctrl, rst_stat;
-       MC_CLIENT mcClientBit;
+       enum mc_client mcClientBit;
        unsigned long flags;
 
        BUG_ON(id < 0 || id >= tegra_num_powerdomains);
@@ -166,7 +189,7 @@ static void mc_flush(int id)
 static void mc_flush_done(int id)
 {
        u32 idx, rst_ctrl;
-       MC_CLIENT mcClientBit;
+       enum mc_client mcClientBit;
        unsigned long flags;
 
        BUG_ON(id < 0 || id >= tegra_num_powerdomains);
@@ -213,21 +236,19 @@ static int tegra_powergate_set(int id, bool new_state)
        return 0;
 }
 
-int tegra_powergate_power_on(int id)
+static int unpowergate_module(int id)
 {
        if (id < 0 || id >= tegra_num_powerdomains)
                return -EINVAL;
-
        return tegra_powergate_set(id, true);
 }
 
-int tegra_powergate_power_off(int id)
+static int powergate_module(int id)
 {
        if (id < 0 || id >= tegra_num_powerdomains)
                return -EINVAL;
 
        mc_flush(id);
-
        return tegra_powergate_set(id, false);
 }
 
@@ -245,7 +266,6 @@ int tegra_powergate_is_powered(int id)
 int tegra_powergate_remove_clamping(int id)
 {
        u32 mask;
-
        if (id < 0 || id >= tegra_num_powerdomains)
                return -EINVAL;
 
@@ -265,43 +285,173 @@ int tegra_powergate_remove_clamping(int id)
        return 0;
 }
 
-/* Must be called with clk disabled, and returns with clk enabled */
-static int tegra_powergate_reset_module(struct clk *clk)
+static void get_clk_info(int id)
+{
+       int idx;
+
+       for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) {
+               if (!powergate_partition_info[id].clk_info[idx].clk_name)
+                       break;
+               powergate_partition_info[id].
+                               clk_info[idx].clk_ptr =
+                                       tegra_get_clock_by_name(
+                       powergate_partition_info[id].clk_info[idx].clk_name);
+       }
+}
+
+static int partition_clk_enable(int id)
 {
        int ret;
+       u32 idx;
+       struct clk *clk;
+
+       BUG_ON(id < 0 || id >= tegra_num_powerdomains);
+
+       for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) {
+               clk = powergate_partition_info[id].clk_info[idx].clk_ptr;
+               if (!clk)
+                       break;
+
+               if (!powergate_partition_info[id].clk_info[idx].only_reset) {
+                       ret = clk_prepare_enable(clk);
+                       if (ret)
+                               goto err_clk_en;
+               }
+       }
+
+       return 0;
 
-       tegra_periph_reset_assert(clk);
+err_clk_en:
+       WARN(1, "Could not enable clk %s", clk->name);
+       while (idx--) {
+               if (!powergate_partition_info[id].clk_info[idx].only_reset) {
+                       clk = powergate_partition_info[id].
+                                               clk_info[idx].clk_ptr;
+                       clk_disable_unprepare(clk);
+               }
+       }
+
+       return ret;
+}
+
+static int is_partition_clk_disabled(int id)
+{
+       u32 idx;
+       struct clk *clk;
+       int ret = 0;
+
+       BUG_ON(id < 0 || id >= tegra_num_powerdomains);
+
+       for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) {
+               clk = powergate_partition_info[id].clk_info[idx].clk_ptr;
+               if (!clk)
+                       break;
+
+               if (!powergate_partition_info[id].clk_info[idx].only_reset) {
+                       if (tegra_is_clk_enabled(clk)) {
+                               ret = -1;
+                               break;
+                       }
+               }
+       }
+
+       return ret;
+}
+
+static void partition_clk_disable(int id)
+{
+       u32 idx;
+       struct clk *clk;
+
+       BUG_ON(id < 0 || id >= tegra_num_powerdomains);
+
+       for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) {
+               clk = powergate_partition_info[id].clk_info[idx].clk_ptr;
+               if (!clk)
+                       break;
+
+               if (!powergate_partition_info[id].clk_info[idx].only_reset)
+                       clk_disable_unprepare(clk);
+       }
+}
+
+static void powergate_partition_assert_reset(int id)
+{
+       u32 idx;
+
+       BUG_ON(id < 0 || id >= tegra_num_powerdomains);
+
+       for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) {
+               if (!powergate_partition_info[id].clk_info[idx].clk_ptr)
+                       break;
+               tegra_periph_reset_assert(
+                       powergate_partition_info[id].
+                                               clk_info[idx].clk_ptr);
+               }
+}
+
+static void powergate_partition_deassert_reset(int id)
+{
+       u32 idx;
+
+       BUG_ON(id < 0 || id >= tegra_num_powerdomains);
+
+       for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) {
+               if (!powergate_partition_info[id].clk_info[idx].clk_ptr)
+                       break;
+               tegra_periph_reset_deassert(
+                       powergate_partition_info[id].clk_info[idx].clk_ptr);
+       }
+}
+
+/* Must be called with clk disabled, and returns with clk disabled */
+static int tegra_powergate_reset_module(int id)
+{
+       int ret;
+
+       powergate_partition_assert_reset(id);
 
        udelay(10);
 
-       ret = clk_enable(clk);
+       ret = partition_clk_enable(id);
        if (ret)
                return ret;
 
        udelay(10);
 
-       tegra_periph_reset_deassert(clk);
+       powergate_partition_deassert_reset(id);
+
+       partition_clk_disable(id);
 
        return 0;
 }
 
-/* Must be called with clk disabled, and returns with clk enabled */
-int tegra_powergate_sequence_power_up(int id, struct clk *clk)
+/*
+ * Must be called with clk disabled, and returns with clk disabled
+ * Drivers should enable clks for partition. Unpowergates only the
+ * partition.
+ */
+int tegra_unpowergate_partition(int id)
 {
        int ret;
 
-       if (tegra_powergate_is_powered(id))
-               return tegra_powergate_reset_module(clk);
+       /* If first clk_ptr is null, fill clk info for the partition */
+       if (!powergate_partition_info[id].clk_info[0].clk_ptr)
+               get_clk_info(id);
 
-       tegra_periph_reset_assert(clk);
+       if (tegra_powergate_is_powered(id))
+               return tegra_powergate_reset_module(id);
 
-       ret = tegra_powergate_power_on(id);
+       ret = unpowergate_module(id);
        if (ret)
                goto err_power;
 
-       ret = clk_prepare_enable(clk);
+       powergate_partition_assert_reset(id);
+
+       /* Un-Powergating fails if all clks are not enabled */
+       ret = partition_clk_enable(id);
        if (ret)
-               goto err_clk;
+               goto err_clk_on;
 
        udelay(10);
 
@@ -310,17 +460,121 @@ int tegra_powergate_sequence_power_up(int id, struct clk *clk)
                goto err_clamp;
 
        udelay(10);
-       tegra_periph_reset_deassert(clk);
+       powergate_partition_deassert_reset(id);
 
        mc_flush_done(id);
 
+       /* Disable all clks enabled earlier. Drivers should enable clks */
+       partition_clk_disable(id);
+
        return 0;
 
 err_clamp:
-       clk_disable_unprepare(clk);
-err_clk:
-       tegra_powergate_power_off(id);
+       partition_clk_disable(id);
+err_clk_on:
+       powergate_module(id);
 err_power:
+       WARN(1, "Could not Un-Powergate %d", id);
+       return ret;
+}
+
+/*
+ * Must be called with clk disabled, and returns with clk enabled
+ * Unpowergates the partition and enables all required clks.
+ */
+int tegra_unpowergate_partition_with_clk_on(int id)
+{
+       int ret = 0;
+
+#ifndef CONFIG_ARCH_TEGRA_2x_SOC
+       /* Restrict this functions use to few partitions */
+       BUG_ON(id != TEGRA_POWERGATE_SATA && id != TEGRA_POWERGATE_PCIE);
+#else
+       /* Restrict this functions use to few partitions */
+       BUG_ON(id != TEGRA_POWERGATE_PCIE);
+#endif
+
+       ret = tegra_unpowergate_partition(id);
+       if (ret)
+               goto err_unpowergating;
+
+       /* Enable clks for the partition */
+       ret = partition_clk_enable(id);
+       if (ret)
+               goto err_unpowergate_clk;
+
+       return ret;
+
+err_unpowergate_clk:
+       tegra_powergate_partition(id);
+       WARN(1, "Could not Un-Powergate %d, err in enabling clk", id);
+err_unpowergating:
+       WARN(1, "Could not Un-Powergate %d", id);
+       return ret;
+}
+
+/*
+ * Must be called with clk disabled. Powergates the partition only
+ */
+int tegra_powergate_partition(int id)
+{
+       int ret;
+
+       /* If first clk_ptr is null, fill clk info for the partition */
+       if (powergate_partition_info[id].clk_info[0].clk_ptr)
+               get_clk_info(id);
+       powergate_partition_assert_reset(id);
+
+       /* Powergating is done only if refcnt of all clks is 0 */
+       ret = is_partition_clk_disabled(id);
+       if (ret)
+               goto err_clk_off;
+
+       ret = powergate_module(id);
+       if (ret)
+               goto err_power_off;
+
+       return 0;
+
+err_power_off:
+       WARN(1, "Could not Powergate Partition %d", id);
+err_clk_off:
+       WARN(1, "Could not Powergate Partition %d, all clks not disabled", id);
+       return ret;
+}
+
+int tegra_powergate_partition_with_clk_off(int id)
+{
+       int ret = 0;
+
+#ifndef CONFIG_ARCH_TEGRA_2x_SOC
+       /* Restrict functions use to selected partitions */
+       BUG_ON(id != TEGRA_POWERGATE_PCIE && id != TEGRA_POWERGATE_SATA &&
+               id != TEGRA_POWERGATE_3D && id != TEGRA_POWERGATE_3D1 &&
+               id != TEGRA_POWERGATE_MPE);
+#else
+       /* Restrict functions use to selected partitions */
+       BUG_ON(id != TEGRA_POWERGATE_PCIE && id != TEGRA_POWERGATE_MPE &&
+               id != TEGRA_POWERGATE_3D);
+#endif
+       /* Disable clks for the partition */
+       partition_clk_disable(id);
+
+       ret = is_partition_clk_disabled(id);
+       if (ret)
+               goto err_powergate_clk;
+
+       ret = tegra_powergate_partition(id);
+       if (ret)
+               goto err_powergating;
+
+       return ret;
+
+err_powergate_clk:
+       WARN(1, "Could not Powergate Partition %d, all clks not disabled", id);
+err_powergating:
+       partition_clk_enable(id);
+       WARN(1, "Could not Powergate Partition %d", id);
        return ret;
 }
 
@@ -352,7 +606,7 @@ int __init tegra_powergate_init(void)
        return 0;
 }
 
-const char* tegra_powergate_get_name(int id)
+const char *tegra_powergate_get_name(int id)
 {
        if (id < 0 || id >= tegra_num_powerdomains)
                return "invalid";
index 25a6d6b..43df231 100644 (file)
@@ -161,6 +161,15 @@ int tegra_register_clk_rate_notifier(struct clk *c, struct notifier_block *nb);
 void tegra_unregister_clk_rate_notifier(
        struct clk *c, struct notifier_block *nb);
 int tegra_clk_cfg_ex(struct clk *c, enum tegra_clk_ex_param p, u32 setting);
+
+/**
+ * tegra_is_clk_enabled - get info if the clk is enabled or not
+ * @clk: clock source
+ *
+ * Returns refcnt.
+ */
+int tegra_is_clk_enabled(struct clk *clk);
+
 #endif
 
 #endif /* __LINUX_CLK_TEGRA_H_ */