diff options
author | Karan Jhavar <kjhavar@nvidia.com> | 2011-05-16 17:00:43 -0700 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2011-11-30 21:47:21 -0800 |
commit | 939622838082223cf57304a1252808641da5c5a8 (patch) | |
tree | 8f27981a6f35fc631a7f19740f852cf65b45143e /arch/arm/mach-tegra/powergate.c | |
parent | 8ffe7f45623875526685d9d4ef5096dcee7a5e38 (diff) |
ARM: tegra: power: Refactored kernel powergate code
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: Rc0aac0edd4e693c15d22d998c882fceeeb85765d
Diffstat (limited to 'arch/arm/mach-tegra/powergate.c')
-rw-r--r-- | arch/arm/mach-tegra/powergate.c | 353 |
1 files changed, 304 insertions, 49 deletions
diff --git a/arch/arm/mach-tegra/powergate.c b/arch/arm/mach-tegra/powergate.c index 8f2fb2eb8969..9d811c84ce68 100644 --- a/arch/arm/mach-tegra/powergate.c +++ b/arch/arm/mach-tegra/powergate.c @@ -31,8 +31,10 @@ #include <mach/iomap.h> #include <mach/powergate.h> +#include "clock.h" + #define PWRGATE_TOGGLE 0x30 -#define PWRGATE_TOGGLE_START (1 << 8) +#define PWRGATE_TOGGLE_START (1 << 8) #define REMOVE_CLAMPING 0x34 @@ -41,7 +43,7 @@ #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, @@ -61,42 +63,64 @@ 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_NUM_POWERGATE] = { - [TEGRA_POWERGATE_CPU] = { "cpu0", {MC_CLIENT_LAST} }, - [TEGRA_POWERGATE_L2] = { "l2", {MC_CLIENT_LAST} }, +static struct powergate_partition powergate_partition_info[TEGRA_NUM_POWERGATE] = { + [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} }, + {MC_CLIENT_ISP, MC_CLIENT_VI, MC_CLIENT_LAST}, + {{"isp", false}, {"vi", false}, + {"csi", false} }, }, #if !defined(CONFIG_ARCH_TEGRA_2x_SOC) - [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} }, + [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} }, }, #endif }; @@ -128,7 +152,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_POWERGATE); @@ -156,7 +180,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_POWERGATE); @@ -203,21 +227,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_POWERGATE) 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_POWERGATE) return -EINVAL; mc_flush(id); - return tegra_powergate_set(id, false); } @@ -235,7 +257,6 @@ bool tegra_powergate_is_powered(int id) int tegra_powergate_remove_clamping(int id) { u32 mask; - if (id < 0 || id >= TEGRA_NUM_POWERGATE) return -EINVAL; @@ -255,43 +276,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_POWERGATE); + + 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_enable(clk); + if (ret) + goto err_clk_en; + } + } + + return 0; + +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(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_POWERGATE); + + 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_POWERGATE); + + 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(clk); + } +} + +static void powergate_partition_assert_reset(int id) +{ + u32 idx; + + BUG_ON(id < 0 || id >= TEGRA_NUM_POWERGATE); + + 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_POWERGATE); + + 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; - tegra_periph_reset_assert(clk); + 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_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); @@ -300,21 +451,125 @@ 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(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; } -const char* tegra_powergate_get_name(int id) +const char *tegra_powergate_get_name(int id) { if (id < 0 || id >= TEGRA_NUM_POWERGATE) return "invalid"; |