diff options
author | Karan Jhavar <kjhavar@nvidia.com> | 2011-05-16 17:00:43 -0700 |
---|---|---|
committer | Jeremy Wyman <jwyman@nvidia.com> | 2011-06-29 21:30:05 -0700 |
commit | 6047882d3c873461db65f7b1bceb57e9cb3826c0 (patch) | |
tree | 28f68fb7e1a0f32c228b04bf9628c1490d999cfa /arch/arm | |
parent | cc9652dc5a7564a5c9d897b1fc18cda8f3affd42 (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
Change-Id: Ia25cf38d2f1e6df1ee0e1a6d8f46b9674a5ed24b
Reviewed-on: http://git-master/r/38815
Reviewed-by: Karan Jhavar <kjhavar@nvidia.com>
Tested-by: Karan Jhavar <kjhavar@nvidia.com>
Reviewed-by: Jin Qian <jqian@nvidia.com>
Reviewed-by: Narendra Damahe <ndamahe@nvidia.com>
Reviewed-by: Aleksandr Frid <afrid@nvidia.com>
Reviewed-by: Chih-Lung Huang <lhuang@nvidia.com>
Reviewed-by: Jeremy Wyman <jwyman@nvidia.com>
Diffstat (limited to 'arch/arm')
-rw-r--r-- | arch/arm/mach-tegra/clock.c | 6 | ||||
-rw-r--r-- | arch/arm/mach-tegra/common.c | 12 | ||||
-rw-r--r-- | arch/arm/mach-tegra/include/mach/clk.h | 8 | ||||
-rw-r--r-- | arch/arm/mach-tegra/include/mach/powergate.h | 37 | ||||
-rw-r--r-- | arch/arm/mach-tegra/platsmp.c | 2 | ||||
-rw-r--r-- | arch/arm/mach-tegra/powergate.c | 351 |
6 files changed, 355 insertions, 61 deletions
diff --git a/arch/arm/mach-tegra/clock.c b/arch/arm/mach-tegra/clock.c index da7ca265bf6b..5c9b0bf62c7e 100644 --- a/arch/arm/mach-tegra/clock.c +++ b/arch/arm/mach-tegra/clock.c @@ -660,6 +660,12 @@ void tegra_periph_reset_assert(struct clk *c) } EXPORT_SYMBOL(tegra_periph_reset_assert); +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) { diff --git a/arch/arm/mach-tegra/common.c b/arch/arm/mach-tegra/common.c index 6d513e94ee3f..bff566ec1de7 100644 --- a/arch/arm/mach-tegra/common.c +++ b/arch/arm/mach-tegra/common.c @@ -212,14 +212,14 @@ void __init tegra_init_cache(void) 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 } diff --git a/arch/arm/mach-tegra/include/mach/clk.h b/arch/arm/mach-tegra/include/mach/clk.h index 549944fa8342..6bee241a99df 100644 --- a/arch/arm/mach-tegra/include/mach/clk.h +++ b/arch/arm/mach-tegra/include/mach/clk.h @@ -57,4 +57,12 @@ 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); +/** + * 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 diff --git a/arch/arm/mach-tegra/include/mach/powergate.h b/arch/arm/mach-tegra/include/mach/powergate.h index 6f79b047d99d..9238dd51157d 100644 --- a/arch/arm/mach-tegra/include/mach/powergate.h +++ b/arch/arm/mach-tegra/include/mach/powergate.h @@ -47,13 +47,40 @@ struct clk; -int tegra_powergate_power_on(int id); -int tegra_powergate_power_off(int id); bool tegra_powergate_is_powered(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_ */ diff --git a/arch/arm/mach-tegra/platsmp.c b/arch/arm/mach-tegra/platsmp.c index a208041ba966..340b8ed264ec 100644 --- a/arch/arm/mach-tegra/platsmp.c +++ b/arch/arm/mach-tegra/platsmp.c @@ -383,7 +383,7 @@ static int power_up_cpu(unsigned int cpu) power gates. Bail out if direct power on also failed */ if (!is_cpu_powered(cpu)) { - ret = tegra_powergate_power_on(TEGRA_CPU_POWERGATE_ID(cpu)); + ret = tegra_unpowergate_partition(TEGRA_CPU_POWERGATE_ID(cpu)); if (ret) goto fail; diff --git a/arch/arm/mach-tegra/powergate.c b/arch/arm/mach-tegra/powergate.c index bceacdef8339..7943acb593bf 100644 --- a/arch/arm/mach-tegra/powergate.c +++ b/arch/arm/mach-tegra/powergate.c @@ -33,8 +33,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 @@ -43,7 +45,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, @@ -63,42 +65,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 }; @@ -130,7 +154,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); @@ -158,7 +182,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); @@ -205,21 +229,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); } @@ -237,7 +259,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; @@ -257,43 +278,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); @@ -302,23 +453,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"; |